Памятка по языку P4

PDF

Базовые типы данных

typedef

Служит для определения дополнительного имени типа.

typedef bit<48> macAddr_t; 	// Объявление нового типа для 48-битового MAC-адреса Ethernet
typedef bit<32> ip4Addr_t;	// Объявление нового типа для 32-битового адреса IPv4

header

Упорядоченный набор элементов (полей заголовка). Заголовок обязательно включает скрытый элемент (бит) validity. Для проверки и изменения этого бита служат функции isValid(), setValid(), setInvalid().

header ethernet_t {
	macAddr_t dstAddr;	// Поле заголовка с MAC-адресом получателя
	macAddr_t srcAddr;	// Поле заголовка с MAC-адресом отправителя
	bit<16> type;		// Поле заголовка, указывающее тип кадра (EtherType)
}

Объявление переменных для заголовков имеет вид

ethernet_t ethernet;		// Переменная типа ethernet_t

Для доступа к полям заголовков используется нотация с точкой, как показано ниже.

macAddr_t src = ethernet.srcAddr;	// Переменная src получает MAC-адрес отправителя

struct

Структуры в P4 представляют собой неупорядоченные наборы элементов.

struct headers_t {
	ethernet_t ethernet;	// Структура, содержащая 1 заголовок типа ethernet_t
}

Стеки заголовков

Стек заголовков представляет собой индексированный массив элементов типа header.

header label_t {	// Объявление заголовка label_t (метка).
	bit<20> label;	// Собственно метка.
	bit bos;	// Флаг последней метки в стеке.
}

struct header_t {	// Объявление стека меток.
	label_t[10] labels;	// Стек меток.
}

header_t hdr;		// Переменная стека меток.

action pop_label() {	// Удаление метки из стека.
	hdr.labels.pop_front(1); // Выталкивание метки.
}

action push_label(in bit<20> label) {	// Добавление метки в стек.
	hdr.labels.push_front(1);		// Вталкивание метки.
	hdr.labels[0].setValid();		// Объявление метки 0 действительной.
	hdr.labels[0] = {label, 0};
}

Операторы и выражения

Объявление и назначение локальных метаданных

bit<16> tmp1; bit<16> tmp2;	// Переменные tmp1 и tmp1 типа bit<16>
tmp1 = hdr.ethernet.type;	// Переменная tmp1 получает значение EtherType из заголовка пакета

«Нарезка» и объединение (конкатенация) битов

tmp2 = tmp1[7:0] ++ tmp1[15:8];	// Переменная tmp2 получает значение объединения битов 0-7
					// из переменной tmp1 с битами 8-15 из той же переменной.
					// В данном случае это перестановка старшего и младшего байтов

Сложение, вычитание и приведение типов

tmp2 = tmp1 + tmp1 — (bit<16>)tmp1[7:0];	// Переменная tmp2 получает значение суммы tmp1 и 
						// старшего байта tmp1

Побитовые операции

tmp2 = (~tmp1 & tmp1) | (tmp1 ^ tmp1);	// Переменная tmp2 получает значение 0, поскольку 
						// операции И для числа и его «отрицания», а также 
						// Исключительное ИЛИ с самим собой дают 0, а 
						// ИЛИ для двух нулей также дает 0.
tmp2 = tmp1 << 3;				// Переменная tmp2 получает значение смещенных на
						// три позиции влево битов переменной tmp1. Три 
						// младших бита будут иметь значение 0.

Действия

Операции на основании входных данных от плоскости управления.

action set_next_hop(bit<32> next_hop) {		// Установка следующего интервала пересылки
	if (next_hop == 0) {				// Если параметр next_hop еще не задан,	
		metadata.next_hop = hdr.ipv4.dst;	// в поле метаданных помещается адрес получателя
							// из заголовка IP в пакете.
	} else {					// В противном случае поле метаданных
		metadata.next_hop = next_hop;		// получает значение параметра next_hop.
	}
}

Операции на основании входных параметров от плоскости данных.

action swap_mac(inout bit<48> x,	// Перестановка MAC-адресов отправителя и получателя,
		 inout bit<48> y) {	// переданных параметрами действия.
	bit<48> tmp = x;		// Сохранение адреса x во временной переменной.
	x = y;				// Назначение переменной x значения переменной y.
	y = tmp;			// Назначение переменной y исходного значения переменной x.
}

Операции на основании входных параметров от плоскостей данных и управления.

action forward(in bit<9> p, bit<48> d) {	// Задание выходного порта и адреса получателя.
	standard_metadata.egress_spec = p;	// Указание выходного порта в метаданных по параметру.
	headers.ethernet.dstAddr = d;		// Указание адреса получателя по параметру.
}

Удаление заголовка из пакета (декапсуляция).

action decap_ip_ip() {			// Декапсуляция внешнего заголовка IP.
	hdr.ipv4 = hdr.inner_ipv4;	// Перенос внутреннего заголовка во внешний.
	hdr.inner_ipv4.setInvalid();	// Объявление внутреннего заголовка недействительным.
}

Таблицы

Ниже приведен пример определения таблицы для поска по максимальному совпадению префиксов.

table ipv4_lpm {				// Таблица сопоставления по префиксам
	key = {
		hdr.ipv4.dstAddr : lpm;	// Выполняется сопоставление lpm для поиска самого длинного
						// префикса. Стандартные типы сопоставления - exact, ternary, 
	}					// lpm.
	// Задание возможных действий.
	actions = {
		ipv4_forward;		// Пересылка IPv4.
		Drop;			// Отбрасывание.
		NoAction;		// Нет действий.
	}
	// Свойства таблицы
	size = 1024;			// Число записей
	default_action = NoAction();	// Используемое при отсутствии совпадений действие.
}

Поток управления

Метод apply служит для вызова блоков «сопоставление-действие».

apply {		// Ветвление по признаку действительности заголовка.
	if (hdr.ipv4.isValid()) {	// Если заголовок пакета IPv4 действителен,
		ipv4_lpm.apply();	// выполняется сопоставление по самому длинному префиксу.
	}
	// Ветвление по результатам поиска в таблице
	if (local_ip_table.apply().hit) {	
		send_to_cpu();		// Передача пакета в порт CPU
	}
	// Ветвление для выбора действия в таблице.
	switch (table1.apply().action_run) { 
		action1: { table2.apply(); }
		action2: { table3.apply(); }
	}
}

Синтаксический анализ заголовков

Использование внешнего элемента packet_in для входного пакета.

extern packet_in {
	void extract<T>(out T hdr);			// Извлечение заголовка в выходную переменную T.
	void extract<T>(out T hdr,in bit<32> n);	// Назначение значения выходной переменной T
							// (заголовок) по входной переменной n.
	T lookahead<T>();				// Извлечение заголовка в T.
	void advance(in bit<32> n);			// Перемещение указателя текущей позиции на n.
	bit<32> length();				// Размер пакета.	
}

Анализ начинается с предопределенного состояния start.

state start {
	transition parse_ethernet;	// Переход к анализу заголовка Ethernet.
}

Заданное пользователем состояние анализатора для определения типа пакета.

state parse_ethernet {
	packet.extract(hdr.ethernet);			// Извлечение заголовка Ethernet.
	transition select(hdr.ethernet.type) {	// Определение типа пакета.
		0x800: parse_ipv4;			// Анализ заголовка IP при EtherType=IP.
		default: accept;			// Восприятие прочих пакетов без дополнительного
	}						// анализа.
}

Определения для заголовков IPv4 и IPv6.

header ip46_t {
	bit<4> version;	// Версия протокола IP.
	bit<4> reserved;
}

Анализ стека заголовков.

state parse_labels {					// Анализ меток в заголовке
	packet.extract(hdr.labels.next);		// Извлечение метки
	transition select(hdr.labels.last.bos) {
		0: parse_labels; 			// Цикл анализа меток.
		1: guess_labels_payload;		// Последняя метка
	}
}

Анализ данных после стека заголовков.

state guess_labels_payload {				// Анализ содержимого.
	transition select(packet.lookahead<ip46_t>().version) {
		4 : parse_inner_ipv4;			// Пакет IPv4
		6 : parse_inner_ipv6;			// Пакет IPv6
		default : parse_inner_ethernet;	// Кадр Ethernet.
	}
}

Сборка пакетов после обработки

Внешний элемент packet_out используется для отправки обработанных пакетов.

extern packet_out {
	void emit<T>(in T hdr);
}
apply {		// Включение действительных заголовков в пакет
	packet.emit(hdr.ethernet);	// Отправка пакета.
}

Архитектура V1Model

Базовые внешние элементы архитектуры перечислены ниже.

extern void truncate(in bit<32> length);
extern void resubmit<T>(in T x);
extern void recirculate<T>(in T x);
enum CloneType { I2E, E2I }
extern void clone(in CloneType type, in bit<32> session);
// Элементы конвейера v1model.
parser Parser<H, M>(packet_in pkt,
		     out H hdr,
		     inout M meta,
		     inout standard_metadata_t std_meta);
control VerifyChecksum<H, M>(inout H hdr, inout M meta );
control Ingress<H, M>(inout H hdr,
			inout M meta,
			inout standard_metadata_t std_meta);
control Egress<H, M>(inout H hdr,
			inout M meta,
			inout standard_metadata_t std_meta);
control ComputeChecksum<H, M>(inout H hdr, inout M meta );
control Deparser<H>( packet_out b, in H hdr);
// Коммутатор v1model
package V1Switch<H, M>(Parser<H, M> p,
			VerifyChecksum<H, M> vr,
			Ingress<H, M> ig,
			Egress<H, M> eg,
			ComputeChecksum<H, M> ck,
			Deparser<H> d);

Стандартные метаданные V1Model

struct standard_metadata_t {
	bit<9>	ingress_port;
	bit<9>	egress_spec;
	bit<9>	egress_port;
	bit<32>	clone_spec;
	bit<32>	instance_type;
	bit<1>	drop;
	bit<16>	recirculate_port;
	bit<32>	packet_length;
	bit<32>	enq_timestamp;
	bit<19>	enq_qdepth;
	bit<32>	deq_timedelta;
	bit<19>	deq_qdepth;
	bit<48>	ingress_global_timestamp;
	bit<48>	egress_global_timestamp;
	bit<32>	lf_field_list;
	bit<16>	mcast_grp;
	bit<32>	resubmit_flag;
	bit<16>	egress_rid;
	bit<1>	checksum_error;
	bit<32>	recirculate_flag;
}

Счетчики и регистры V1Model

Счетчики

counter(8192, CounterType.packets) c;
action count(bit<32> index) {
	c.count(index);	// Инкрементирование счетчика пакетов по индексу.
}

Регистры

register<bit<48>>(16384) r;
action ipg(out bit<48> ival, bit<32> x) {
	bit<48> last;
	bit<48> now;
	r.read(last, x);
	now = std_meta.ingress_global_timestamp;
	ival = now - last;
	r.write(x, now);
}

Николай Малых

nmalykh@protocols.ru




Краткий обзор репозиториев P4 на Github

PDF

Здесь кратко описаны репозитории общего пользования p4lang.

Если нужна компиляция исходного кода P414 или P416 для модели поведения bmv2 с использованием консольного интерфейса или thrift API для работы с таблицами, потребуется локально установить и собрать два приведенных ниже репозитория.

  • p4c — прототип компилятора P416 (поддерживается также код P414);
  • behavioral-model — переопределение модели поведения на языке C++ без автоматического создания кода.

Репозитории p4lang

В Github p4lang является именем «организации», поддерживающей набор репозиториев, связанных с языком P4. Ниже перечислены эти репозитории по состоянию на сентябрь 2020 г.

behavioral-model

Переопределение модели поведения behavioral-model (ее часто называют bmv2 — сокращение от Behavioral Model Version 2) на языке C++ без автоматического создания кода.

Первой версией модели служил вывод кода из репозитория p4c-behavioral (компилятор P4 в C). Изменение программы P4 требовало повторной компиляции в новую программу C и последующей ее компиляции. Модель bmv2 больше напоминает интерпретатор для всех возможных программ P4, который настраивает свое поведение в соответствии с конфигурационным файлом bmv2 JSON, создаваемым компилятором p4c. При изменении программы P4 требуется повторная компиляция лишь этой программы без повтора компиляции bmv2.

education

P4 для обучения (материалы рабочей группы P4 Education).

governance

Wiki с документами проекта P4. В настоящее время репозиторий пуст.

grpc (ветвь grpc/grpc)

Обобщенная модель работы с вызовами удаленных процедур (C, C++, Python, Ruby, Objective-C, PHP, C#).

hackathons

Код, разработанный на мероприятиях P4 Hackathon.

mininet (ветвь mininet/mininet)

Эмулятор для работы с программно-определяемыми сетями (SDN), http://mininet.org.

ntf

Платформа для сетевых тестов.

p4-applications

Репозиторий рабочей группы P4 Applications, содержащий приложения и документацию по приложениям телеметрии в P4.

p4-build

Инфраструктура, требуемая для генерации, сборки и установки библиотеки PD для программ P4.

p4-hlir

Написанный на языке Python компилятор для программ P414 (v1.0.x и v1.1.x), генерирующий высокоуровневое промежуточное представление (High Level Intermediate Representation), на основе которого можно создавать компилятор back-end. Создает в памяти объекты Python, представляющие объекты код P4 (заголовки, таблицы, списки полей и т. п.). Выполняет независимые от целевой платформы семантические проверки, включая ссылки на неопределенные таблицы или поля, исключение неиспользуемых таблиц и действий.

p4-hlir не понимает программ P416.

p4-spec

Спецификации языка P4 разных версий, PSA (Portable Switch Architecture), INT (Inband Network Telemetry).

p4app

Инструменты для сборки, запуска, отладки и тестирования программ P4.

p4c

Прототип компилятора (front-end) P416, поддерживающий также программы P414. Репозиторий включает также bmv2 и другие варианты компиляторов back-end.

p4c-behavioral

Устаревший компилятор P4 для модели поведения (behavioral model). Заменен behavioral-model.

Компилятор p4c-behavioral использует p4-hlir в качестве front-end для синтаксического анализа исходного кода и дает на выходе промежуточное представление IR, из которого создается код C/C++ для модели поведения версии 1 (не подходит для bmv2).

При установке этого репозитория могут возникать конфликты с p4c-bm, поскольку оба устанавливают модуль Python p4c-bm.

p4c-bm

Устаревшая программа генерации конфигурационных файлов JSON для bmv2, а также кода C/C++ PD. Не развивается. Компилятор p4c-bm использует p4-hlir в качестве front-end для синтаксического анализа исходного кода и создает промежуточное представление IR, из которого может быть сгенерирован конфигурационный файл bmv2 JSON. Может также генерировать код C++ PD.

p4-constraints

Расширение языка P4 для анонсирования ограничений. См. презентацию. Ограничения могут быть реализованы с помощью библиотеки p4-constraints, представленной в репозитории.

p4factory

Устаревшая тестовая реализация спецификации INT (Inband Network Telemetry).

p4lang.github.io

Некая информация с сайта P4.org, в том числе анонсы мероприятий.

p4ofagent

Агент Openflow для плоскости данных P4.

p4runtime

Спецификации P4Runtime — API плоскости управления.

p4runtime-shell

Интерактивная оболочка (shell) Python для P4Runtime.

papers

Несколько старых статей, связанных с P4.

PI

Реализация сервера P4Runtime.

protobuf (ветвь protocolbuffers/protobuf)

Реализация механизмов Protocol Buffers для сериализации структурированных данных от компании Google.

ptf

Модель тестирования плоскости данных на основе Python.

rules_protobuf (ветвь pubref/rules_protobuf)

Правила Bazel для создания буферов протоколов и служб gRPC (java, c++, go и т. п.)

SAI (ветвь opencomputeproject/SAI)

Реализация абстрактного интерфейса SAI, определяющего API для независимого от производителя управления элементами пересылки (ASIC, NPU, программные коммутаторы).

scapy-vxlan

Реализация scapy от компании Barefoot с поддержкой дополнительных заголовков пакетов, включая VXLAN.

switch

Программа switch — пример реализации коммутатора на языке P4, включающая API, SAI и Netlink.

third-party

Сторонние программы, от которых зависит p4lang.

thrift (ветвь apache/thrift)

Зеркало репозитория Apache Thrift.

tutorials

Учебные материалы по языку P4.

Николай Малых

nmalykh@protocols.ru




Архитектура переносимых коммутаторов PSA

PDF

Приведенный ниже включаемый файл (include) с переведенными на русский язык комментариями содержит определения для архитектуры PSA. Файл psa.p4 размещается в каталоге p4include пакета p4c (https://github.com/p4lang/p4c/blob/master/p4include/psa.p4).

Этот файл включается в программы P4, работающие с прототипом коммутатора psa_switch на основе модели BMV2.

/* Copyright 2013-present Barefoot Networks, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#ifndef __PSA_P4__
#define __PSA_P4__

#include<core.p4>

#ifndef _PORTABLE_SWITCH_ARCHITECTURE_P4_
#define _PORTABLE_SWITCH_ARCHITECTURE_P4_

/**
 *   Объявления P4-16 для архитектуры PSA
 */

/**
 * Эти типы нужно определить до включения файла архитектуры, а затем 
 * следует определить защиту макросов.
 */
#define PSA_ON_BMV2_CORE_TYPES
#ifdef PSA_ON_BMV2_CORE_TYPES
/* Показанные ниже размеры в битах относятся к платформе BMv2 psa_switch.
 * Размер этих типов не требуется сохранять в других реализациях PSA. 
 * Каждая реализация может указать свои размеры в форме bit<W> с нужным W.
 * Одной из причин приведенного здесь выбора является реализация PSA в
 * модели BMv2. Другая заключается в упрощении компиляции этого файла и 
 * примера программы PSA (P4), включающей файл.
 *
 * Размеры в битах для BMv2 psa_switch выбраны так, чтобы они совпадали с
 * соответствующими типами InHeader. Это упрощает реализацию P4Runtime для
 * BMv2 psa_switch. */

/* В определениях используется typedef, а не type, что просто задает другие
 * имена для типа bit<W> с показанным значением W. В отличие от определений
 * type значения, объявленные с typedef, можно свободно смешивать в 
 * выражениях со значениями типа bit<W>. Значения, объявленные с type,
 * нельзя свободно смешивать, пока они не приведены к соответствующему типу
 * typedef. Это может быть неудобно для арифметических операций, зато можно
 * пометить нужным типом все значения, объявленные с type, при генерации
 * API плоскости управления.
 *
 * Отметим, что размер typedef <name>Uint_t всегда совпадает с type <name>_t. */
typedef bit<32> PortIdUint_t;
typedef bit<32> MulticastGroupUint_t;
typedef bit<16> CloneSessionIdUint_t;
typedef bit<8>  ClassOfServiceUint_t;
typedef bit<16> PacketLengthUint_t;
typedef bit<16> EgressInstanceUint_t;
typedef bit<64> TimestampUint_t;

/* Отметим, что clone_spec в BMv2 simple_switch v1model имеет размер 32 бита, 
 * но используется так, что 16 битов содержат идентификатор сеанса клонирования
 * и другие 16 битов - численный идентификатор field_list. Лишь 16 битов
 * идентификатора сессии можно сравнивать с типов CloneSessionIdUint_t. См.
 * https://github.com/p4lang/behavioral-model/blob/master/targets/simple_switch/simple_switch.cpp 
 */

@p4runtime_translation("p4.org/psa/v1/PortId_t", 32)
type PortIdUint_t         PortId_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroup_t", 32)
type MulticastGroupUint_t MulticastGroup_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionId_t", 16)
type CloneSessionIdUint_t CloneSessionId_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfService_t", 8)
type ClassOfServiceUint_t ClassOfService_t;
@p4runtime_translation("p4.org/psa/v1/PacketLength_t", 16)
type PacketLengthUint_t   PacketLength_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstance_t", 16)
type EgressInstanceUint_t EgressInstance_t;
@p4runtime_translation("p4.org/psa/v1/Timestamp_t", 64)
type TimestampUint_t      Timestamp_t;
typedef error   ParserError_t;

const PortId_t PSA_PORT_RECIRCULATE = (PortId_t) 0xfffffffa;
const PortId_t PSA_PORT_CPU = (PortId_t) 0xfffffffd;

const CloneSessionId_t PSA_CLONE_SESSION_TO_CPU = (CloneSessionId_t) 0;

#endif  // PSA_ON_BMV2_CORE_TYPES

#ifndef PSA_ON_BMV2_CORE_TYPES
#error "Please define the following types for PSA and the PSA_EXAMPLE_CORE_TYPES macro"
// BEGIN:Type_defns
/* В определениях используется typedef, а не type, что просто задает другие
 * имена для типа bit<W> с показанным значением W. В отличие от определений
 * type значения, объявленные с typedef, можно свободно смешивать в 
 * выражениях со значениями типа bit<W>. Значения, объявленные с type,
 * нельзя свободно смешивать, пока они не приведены к соответствующему типу
 * typedef. Это может быть неудобно для арифметических операций, зато можно
 * пометить нужным типом все значения, объявленные с type, при генерации
 * API плоскости управления.
 *
 * Отметим, что размер typedef <name>Uint_t всегда совпадает с type <name>_t. */
typedef bit<unspecified> PortIdUint_t;
typedef bit<unspecified> MulticastGroupUint_t;
typedef bit<unspecified> CloneSessionIdUint_t;
typedef bit<unspecified> ClassOfServiceUint_t;
typedef bit<unspecified> PacketLengthUint_t;
typedef bit<unspecified> EgressInstanceUint_t;
typedef bit<unspecified> TimestampUint_t;

@p4runtime_translation("p4.org/psa/v1/PortId_t", 32)
type PortIdUint_t         PortId_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroup_t", 32)
type MulticastGroupUint_t MulticastGroup_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionId_t", 16)
type CloneSessionIdUint_t CloneSessionId_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfService_t", 8)
type ClassOfServiceUint_t ClassOfService_t;
@p4runtime_translation("p4.org/psa/v1/PacketLength_t", 16)
type PacketLengthUint_t   PacketLength_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstance_t", 16)
type EgressInstanceUint_t EgressInstance_t;
@p4runtime_translation("p4.org/psa/v1/Timestamp_t", 64)
type TimestampUint_t      Timestamp_t;
typedef error   ParserError_t;

const PortId_t PSA_PORT_RECIRCULATE = (PortId_t) unspecified;
const PortId_t PSA_PORT_CPU = (PortId_t) unspecified;

const CloneSessionId_t PSA_CLONE_SESSION_TO_CPU = (CloneSessiontId_t) unspecified;
// END:Type_defns
#endif  // #ifndef PSA_EXAMPLE_CORE_TYPES

// BEGIN:Type_defns2
/* Все типы с InHeader в имени предназначены для передачи лишь значений
 * соответствующих типов из заголовков пакета между устройством PSA и
 * управляющей им программой сервера P4Runtime.
 *
 * Предполагается, что размер будет не меньше, чем у любого устройства PSA
 * для данного типа. Таким образом, эти типы могут также быть полезны для
 * определения заголовков пакета, передаваемых между устройством PSA и
 * другими устройствами без прохождения через сервер P4Runtime (например,
 * для отправки пакетов контроллеру или системе сбора данных с 
 * использованием скорости передачи пакетов выше, чем может обработать 
 * сервер P4Runtime). При использовании для таких целей не предъявляется
 * требования к плоскости данных PSA по автоматическому численному
 * преобразованию этих типов, которое происходило бы при прохождении
 * заголовков через сервер P4Runtime. Все нужные преобразования следует
 * выполнять программисту P4 явно в кода программы.
 *
 * Все размеры должны быть кратны 8, поэтому любое подмножество этих
 * полей можно использовать в одном определении заголовка P4, даже в 
 * реализациях P4, ограничивающих заголовки полями с общим размером, 
 * кратным 8 битам. */

/* См. комментарий выше (PortIdUint_t). */
typedef bit<32> PortIdInHeaderUint_t;
typedef bit<32> MulticastGroupInHeaderUint_t;
typedef bit<16> CloneSessionIdInHeaderUint_t;
typedef bit<8>  ClassOfServiceInHeaderUint_t;
typedef bit<16> PacketLengthInHeaderUint_t;
typedef bit<16> EgressInstanceInHeaderUint_t;
typedef bit<64> TimestampInHeaderUint_t;

@p4runtime_translation("p4.org/psa/v1/PortIdInHeader_t", 32)
type  PortIdInHeaderUint_t         PortIdInHeader_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroupInHeader_t", 32)
type  MulticastGroupInHeaderUint_t MulticastGroupInHeader_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionIdInHeader_t", 16)
type  CloneSessionIdInHeaderUint_t CloneSessionIdInHeader_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfServiceInHeader_t", 8)
type  ClassOfServiceInHeaderUint_t ClassOfServiceInHeader_t;
@p4runtime_translation("p4.org/psa/v1/PacketLengthInHeader_t", 16)
type  PacketLengthInHeaderUint_t   PacketLengthInHeader_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstanceInHeader_t", 16)
type  EgressInstanceInHeaderUint_t EgressInstanceInHeader_t;
@p4runtime_translation("p4.org/psa/v1/TimestampInHeader_t", 64)
type  TimestampInHeaderUint_t      TimestampInHeader_t;
// END:Type_defns2

/* Функции _int_to_header были написаны для преобразования значений типа
 * <name>_t (значение внутри пути данных) в значения типа <name>InHeader_t
 * в заголовке, который будет передан в порт CPU.
 *
 * Функции _header_to_int  были написаны для преобразования значений в
 * обратном направлении, обычно при назначении из заголовка, полученного из
 * CPU, значению, которое будет использоваться в остальном коде программы.
 *
 * Причина этих трех преобразований заключается в том, что каждый из этих
 * типов объявлен через P4_16 type, поэтому без приведения типа значения 
 * можно присваивать лишь идентичному типу. Первое приведение меняет
 * исходный тип на bit<W1> с тем же размером W1. Второе приведение меняет 
 * размер путем добавления нулей в начале или отбрасывания старших битов.
 * Третье приведение меняет тип bit<W2> на окончательный тип с размером W2. */

PortId_t psa_PortId_header_to_int (in PortIdInHeader_t x) {
    return (PortId_t) (PortIdUint_t) (PortIdInHeaderUint_t) x;
}
MulticastGroup_t psa_MulticastGroup_header_to_int (in MulticastGroupInHeader_t x) {
    return (MulticastGroup_t) (MulticastGroupUint_t) (MulticastGroupInHeaderUint_t) x;
}
CloneSessionId_t psa_CloneSessionId_header_to_int (in CloneSessionIdInHeader_t x) {
    return (CloneSessionId_t) (CloneSessionIdUint_t) (CloneSessionIdInHeaderUint_t) x;
}
ClassOfService_t psa_ClassOfService_header_to_int (in ClassOfServiceInHeader_t x) {
    return (ClassOfService_t) (ClassOfServiceUint_t) (ClassOfServiceInHeaderUint_t) x;
}
PacketLength_t psa_PacketLength_header_to_int (in PacketLengthInHeader_t x) {
    return (PacketLength_t) (PacketLengthUint_t) (PacketLengthInHeaderUint_t) x;
}
EgressInstance_t psa_EgressInstance_header_to_int (in EgressInstanceInHeader_t x) {
    return (EgressInstance_t) (EgressInstanceUint_t) (EgressInstanceInHeaderUint_t) x;
}
Timestamp_t psa_Timestamp_header_to_int (in TimestampInHeader_t x) {
    return (Timestamp_t) (TimestampUint_t) (TimestampInHeaderUint_t) x;
}

PortIdInHeader_t psa_PortId_int_to_header (in PortId_t x) {
    return (PortIdInHeader_t) (PortIdInHeaderUint_t) (PortIdUint_t) x;
}
MulticastGroupInHeader_t psa_MulticastGroup_int_to_header (in MulticastGroup_t x) {
    return (MulticastGroupInHeader_t) (MulticastGroupInHeaderUint_t) (MulticastGroupUint_t) x;
}
CloneSessionIdInHeader_t psa_CloneSessionId_int_to_header (in CloneSessionId_t x) {
    return (CloneSessionIdInHeader_t) (CloneSessionIdInHeaderUint_t) (CloneSessionIdUint_t) x;
}
ClassOfServiceInHeader_t psa_ClassOfService_int_to_header (in ClassOfService_t x) {
    return (ClassOfServiceInHeader_t) (ClassOfServiceInHeaderUint_t) (ClassOfServiceUint_t) x;
}
PacketLengthInHeader_t psa_PacketLength_int_to_header (in PacketLength_t x) {
    return (PacketLengthInHeader_t) (PacketLengthInHeaderUint_t) (PacketLengthUint_t) x;
}
EgressInstanceInHeader_t psa_EgressInstance_int_to_header (in EgressInstance_t x) {
    return (EgressInstanceInHeader_t) (EgressInstanceInHeaderUint_t) (EgressInstanceUint_t) x;
}
TimestampInHeader_t psa_Timestamp_int_to_header (in Timestamp_t x) {
    return (TimestampInHeader_t) (TimestampInHeaderUint_t) (TimestampUint_t) x;
}

/// Диапазон поддерживаемых значений для свойств таблицы psa_idle_timeout
enum PSA_IdleTimeout_t {
  NO_TIMEOUT,
  NOTIFY_CONTROL
};

// BEGIN:Metadata_types
enum PSA_PacketPath_t {
    NORMAL,     /// Пакет, полученный ingress и не относящийся к перечисленным ниже.
    NORMAL_UNICAST,   /// Обычный индивидуальный пакет, принятый egress.
    NORMAL_MULTICAST, /// Обычный групповой пакет, принятый egress.
    CLONE_I2E,  /// Пакет, созданный клонированием в ingress и предназначенный
                /// для egress.
    CLONE_E2E,  /// Пакет, созданный клонированием в egress и предназначенный
                /// для egress.
    RESUBMIT,   /// Пакет, принятый в результате операции resubmit.
    RECIRCULATE /// Пакет, принятый в результате операции recirculate.
}

struct psa_ingress_parser_input_metadata_t {
  PortId_t                 ingress_port;
  PSA_PacketPath_t         packet_path;
}

struct psa_egress_parser_input_metadata_t {
  PortId_t                 egress_port;
  PSA_PacketPath_t         packet_path;
}

struct psa_ingress_input_metadata_t {
  // Все эти значения инициализируются архитектурой до начала выполнения
  // бллока управления Ingress.
  PortId_t                 ingress_port;
  PSA_PacketPath_t         packet_path;
  Timestamp_t              ingress_timestamp;
  ParserError_t            parser_error;
}
// BEGIN:Metadata_ingress_output
struct psa_ingress_output_metadata_t {
  // В комментариях к полям приведены исходные значения в момент начала
  // выполнения блока управления Ingress.
  ClassOfService_t         class_of_service; // 0
  bool                     clone;            // false
  CloneSessionId_t         clone_session_id; // не определено
  bool                     drop;             // true
  bool                     resubmit;         // false
  MulticastGroup_t         multicast_group;  // 0
  PortId_t                 egress_port;      // не определено
}
// END:Metadata_ingress_output
struct psa_egress_input_metadata_t {
  ClassOfService_t	class_of_service;
  PortId_t            egress_port;
  PSA_PacketPath_t    packet_path;
  EgressInstance_t    instance;       /// Экземпляр приходит из PacketReplicationEngine
  Timestamp_t         egress_timestamp;
  ParserError_t       parser_error;
}

/// Эта структура является входным (in) параметром для выходного сборщика.
/// Она включает данные для выходного сборщика, позволяющие решить вопрос
/// о рециркуляции пакета.
struct psa_egress_deparser_input_metadata_t {
  PortId_t                 egress_port;
}
// BEGIN:Metadata_egress_output
struct psa_egress_output_metadata_t {
  // В комментариях к полям приведены исходные значения в момент начала
  // выполнения блока управления Egress.
  bool                     clone;         // false
  CloneSessionId_t         clone_session_id; // не определено
  bool                     drop;          // false
}
// END:Metadata_egress_output
// END:Metadata_types

/// При выполнении IngressDeparser функция psa_clone_i2e возвращает true
/// тогда и только тогда, когда создается клон обрабатываемого пакета для 
/// выхода. Какие-либо назначения параметра clone_i2e_meta в
/// IngressDeparser могут выполняться лишь внутри оператора if, который
/// позволяет выполнять такое назначение лишь в случае возврата функцией 
/// psa_clone_i2e(istd) значения true. psa_clone_i2e можно реализовать 
/// путем возврата istd.clone

@pure
extern bool psa_clone_i2e(in psa_ingress_output_metadata_t istd);

/// При выполнении IngressDeparser функция psa_resubmit возвращает true
/// тогда и только тогда, когда пакет представляется заново (resubmit).
/// Какие-либо назначения параметра resubmit_meta в  IngressDeparser
/// могут выполняться лишь внутри оператора if, который  позволяет
/// выполнять такое назначение лишь в случае возврата функцией 
/// psa_resubmit(istd) значения true. psa_resubmit можно реализовать 
/// путем возврата (!istd.drop && istd.resubmit)


@pure
extern bool psa_resubmit(in psa_ingress_output_metadata_t istd);

/// При выполнении IngressDeparser функция psa_normal возвращает true
/// тогда и только тогда, когда пакет передается на выход «обычным» 
/// путем как индивидуальный или групповой. Какие-либо назначения 
/// параметра resubmit_meta в IngressDeparser могут выполняться лишь 
/// внутри оператора if, который позволяет выполнять такое назначение 
/// лишь в случае возврата функцией psa_normal(istd) значения true. 
/// psa_normal можно реализовать путем возврата (!istd.drop && istd.resubmit)

@pure
extern bool psa_normal(in psa_ingress_output_metadata_t istd);

/// При выполнении EgressDeparser функция psa_clone_e2e возвращает true
/// тогда и только тогда, когда создается клон обрабатываемого пакета
/// для выхода. Какие-либо назначения параметра clone_e2e_meta в 
/// EgressDeparser могут выполняться лишь внутри оператора if, который
/// позволяет выполнять такое назначение лишь в случае возврата функцией
/// psa_clone_e2e(istd) значения true. psa_clone_e2e можно реализовать
/// путем возврата istd.clone

@pure
extern bool psa_clone_e2e(in psa_egress_output_metadata_t istd);

/// При выполнении EgressDeparser функция psa_recirculate возвращает true
/// тогда и только тогда, когда пакет передается в рециркуляцию. 
/// Какие-либо назначения параметра recirculate_meta в EgressDeparser
/// могут выполняться лишь внутри оператора if, который позволяет
/// выполнять такое назначение лишь в случае возврата функцией 
/// psa_recirculate(istd) значения true. psa_recirculate можно 
/// реализовать путем возврата (!istd.drop && (edstd.egress_port
/// == PSA_PORT_RECIRCULATE))

@pure
extern bool psa_recirculate(in psa_egress_output_metadata_t istd,
                            in psa_egress_deparser_input_metadata_t edstd);


extern void assert(in bool check);
extern void assume(in bool check);

// BEGIN:Match_kinds
match_kind {
    range,   /// Служит для представления интервалов min..max.
    selector /// Служит для динамического выбора действия с помощью 
             /// внешнего метода ActionSelector.
}
// END:Match_kinds

// BEGIN:Action_send_to_port
/// Меняет выходные метаданные ingress для отправки пакета на выходную
/// обработку с последующим выводом в egress_port (при выходной 
/// обработке пакет может быть отброшен).

/// Это действие не влияет на операции clone или resubmit.

@noWarnUnused
action send_to_port(inout psa_ingress_output_metadata_t meta,
                    in PortId_t egress_port)
{
    meta.drop = false;
    meta.multicast_group = (MulticastGroup_t) 0;
    meta.egress_port = egress_port;
}
// END:Action_send_to_port

// BEGIN:Action_multicast
/// Меняет выходные метаданные ingress для создания копий пакета, 
/// отправляемых на выходную обработку.

/// Это действие не влияет на операции clone или resubmit.

@noWarnUnused
action multicast(inout psa_ingress_output_metadata_t meta,
                 in MulticastGroup_t multicast_group)
{
    meta.drop = false;
    meta.multicast_group = multicast_group;
}
// END:Action_multicast

// BEGIN:Action_ingress_drop
/// Меняет выходные метаданные ingress для для обычной выходной
/// обработки.

/// Это действие не влияет на операцию clone, но предотвращает 
/// повторное представления пакета (resubmit).

@noWarnUnused
action ingress_drop(inout psa_ingress_output_metadata_t meta)
{
    meta.drop = true;
}
// END:Action_ingress_drop

// BEGIN:Action_egress_drop
/// Меняет выходные метаданные egress для передачи пакета из устройства.

/// Это действие не влияет на операцию clone.

@noWarnUnused
action egress_drop(inout psa_egress_output_metadata_t meta)
{
    meta.drop = true;
}
// END:Action_egress_drop

extern PacketReplicationEngine {
    PacketReplicationEngine();
    // Для этого объекта нет методов, вызываемых из программ P4. Метод
    // будет иметь экземпляр с именем, которое плоскость управления
    // может использовать для вызова объекта через API.
}

extern BufferingQueueingEngine {
    BufferingQueueingEngine();
    // Для этого объекта нет методов, вызываемых из программ P4. 
    // См. предыдущий комментарий. 
}

// BEGIN:Hash_algorithms
enum PSA_HashAlgorithm_t {
  IDENTITY,
  CRC32,
  CRC32_CUSTOM,
  CRC16,
  CRC16_CUSTOM,
  ONES_COMPLEMENT16,  /// 16-битовая контрольная сумма с дополнением до 1,
                      /// используемая в заголовках IPv4, TCP и UDP.
  TARGET_DEFAULT      /// Определяется реализацией целевой платформы.
}
// END:Hash_algorithms

// BEGIN:Hash_extern
extern Hash<O> {
  /// Constructor
  Hash(PSA_HashAlgorithm_t algo);

  /// Расчет хэш-значения для данных.
  /// @param data - данные для расчета хэш-значения.
  /// @return - хэш-значение.
  @pure
  O get_hash<D>(in D data);

  /// Расчет хэш-значения для данных с модулем max и добавлением base.
  /// @param base - минимальное возвращаемое значение.
  /// @param data - данные для расчета хэш-значения.
  /// @param max - хэш-значение делится на max.
  ///        Реализация может ограничивать поддерживаемое максимальное
  ///        значение (например, 32 или 256), а также может поддерживать
  ///        для него лишь степени 2. Разработчикам P4 следует выбирать
  ///        такие значения для обеспечения переносимости.
  /// @return (base + (h % max)), где h - хэш-значение.
  @pure
  O get_hash<T, D>(in T base, in D data, in T max);
}
// END:Hash_extern

// BEGIN:Checksum_extern
extern Checksum<W> {
  /// Constructor
  Checksum(PSA_HashAlgorithm_t hash);

  /// Сбрасывает внутреннее состояние и готовит модуль к расчету. Каждый
  /// экземпляр объекта Checksum автоматически инициализируется как при
  /// вызове clear(). Инициализация выполняется при каждом создании 
  /// экземпляра объекта, независимо от применения в анализаторе или 
  /// элементе управления. Все состояния, поддерживаемые объектом  
  /// Checksum независимы между пакетами.
  void clear();

  /// Добавление данных в контрольную сумму.
  void update<T>(in T data);

  /// Получение контрольной суммы для добавленных (и не удаленных) 
  /// с момента последней очистки данных.
  @noSideEffects
  W    get();
}
// END:Checksum_extern

// BEGIN:InternetChecksum_extern
// Контрольная сумма на основе алгоритма ONES_COMPLEMENT16, используемая в 
// IPv4, TCP и UDP. Поддерживается инкрементальное обновление методом 
// subtract (см. IETF RFC 1624).
extern InternetChecksum {
  /// Конструктор
  InternetChecksum();

  /// Сбрасывает внутреннее состояние и готовит модуль к расчету. Каждый
  /// экземпляр объекта InternetChecksum автоматически инициализируется как 
  /// при вызове clear(). Инициализация выполняется при каждом создании 
  /// запуске анализатора или элемента управления, где применяется объект. 
  /// Все состояния, поддерживаемые объектом независимы между пакетами.
  void clear();

  /// Добавляет в расчет контрольной суммы данные data, размер которых
  /// должен быть кратным 16 битам.
  void add<T>(in T data);

  /// Исключает из расчета контрольной суммы данные data, размер которых
  /// должен быть кратным 16 битам.
  void subtract<T>(in T data);

  /// Возвращает контрольную сумму для данных, добавленных (и не удаленных)
  /// после предшествующего вызова clear.
  @noSideEffects
  bit<16> get();

  /// Возвращает состояние расчета контрольной суммы для использования при
  /// последующем вызове метода set_state.
  @noSideEffects
  bit<16> get_state();

  /// Возвращает состояние экземпляра InternetChecksum к возвращенному при
  /// предшествующем вызове метода get_state. Состояние может возвращаться
  /// для одного или разных экземпляров InternetChecksum.
  void set_state(in bit<16> checksum_state);
}
// END:InternetChecksum_extern

// BEGIN:CounterType_defn
enum PSA_CounterType_t {
    PACKETS,
    BYTES,
    PACKETS_AND_BYTES
}
// END:CounterType_defn

// BEGIN:Counter_extern
/// Опосредованный счетчик с n_counters независимых значений, где каждое
/// значение имеет заданный плоскостью данных размер W.

extern Counter<W, S> {
  Counter(bit<32> n_counters, PSA_CounterType_t type);
  void count(in S index);

  /*
  /// API плоскости управления использует 64-битовые значения считчиков. 
  /// Это не указывает размеры счетчиков в плоскости управления. 
  /// Предполагается, что программы управления периодически считывают
  /// значения счетчиков плоскости данных и аккумулирует их в счетчиках
  /// большего размера, в которых максимальное значение достигается реже.
  /// 64-битовые счетчики позволяют работать при скорости порта 100 Гбит/с
  /// в течение 46 лет без переполнения.

  @ControlPlaneAPI
  {
    bit<64> read      (in S index);
    bit<64> sync_read (in S index);
    void set          (in S index, in bit<64> seed);
    void reset        (in S index);
    void start        (in S index);
    void stop         (in S index);
  }
  */
}
// END:Counter_extern

// BEGIN:DirectCounter_extern
extern DirectCounter<W> {
  DirectCounter(PSA_CounterType_t type);
  void count();

  /*
  @ControlPlaneAPI
  {
    W    read<W>      (in TableEntry key);
    W    sync_read<W> (in TableEntry key);
    void set          (in TableEntry key, in W seed);
    void reset        (in TableEntry key);
    void start        (in TableEntry key);
    void stop         (in TableEntry key);
  }
  */
}
// END:DirectCounter_extern

// BEGIN:MeterType_defn
enum PSA_MeterType_t {
    PACKETS,
    BYTES
}
// END:MeterType_defn

// BEGIN:MeterColor_defn
enum PSA_MeterColor_t { RED, GREEN, YELLOW }
// END:MeterColor_defn

// BEGIN:Meter_extern
// Индексируемый измеритель с n_meters независимых состояний.

extern Meter<S> {
  Meter(bit<32> n_meters, PSA_MeterType_t type);

  // Этот метод служит для «перекрашивания» трафика (см. RFC 2698). 
  // «Цвет» пакета перед вызовом метода указан параметром color.
  PSA_MeterColor_t execute(in S index, in PSA_MeterColor_t color);

  // Этот метод служит для «окрашивания» трафика вслепую (см. RFC 2698). 
  // Метод может быть реализован вызовом execute(index, MeterColor_t.GREEN).
  PSA_MeterColor_t execute(in S index);

  /*
  @ControlPlaneAPI
  {
    reset(in MeterColor_t color);
    setParams(in S index, in MeterConfig config);
    getParams(in S index, out MeterConfig config);
  }
  */
}
// END:Meter_extern

// BEGIN:DirectMeter_extern
extern DirectMeter {
  DirectMeter(PSA_MeterType_t type);
  // См. аналогичный метод для extern Meter.
  PSA_MeterColor_t execute(in PSA_MeterColor_t color);
  PSA_MeterColor_t execute();

  /*
  @ControlPlaneAPI
  {
    reset(in TableEntry entry, in MeterColor_t color);
    void setConfig(in TableEntry entry, in MeterConfig config);
    void getConfig(in TableEntry entry, out MeterConfig config);
  }
  */
}
// END:DirectMeter_extern

// BEGIN:Register_extern
extern Register<T, S> {
  /// Создание массива из <size> регистров с неопределенными значениями.
  Register(bit<32> size);
  ///  Создание массива из <size> регистров с заданными значениями.
  Register(bit<32> size, T initial_value);

  @noSideEffects
  T    read  (in S index);
  void write (in S index, in T value);

  /*
  @ControlPlaneAPI
  {
    T    read<T>      (in S index);
    void set          (in S index, in T seed);
    void reset        (in S index);
  }
  */
}
// END:Register_extern

// BEGIN:Random_extern
extern Random<T> {

  /// Возвращает случайное значение из диапазона [min, max].
  /// Реализациям разрешается поддерживать лишь диапазоны, где
  /// значение (max - min + 1) является степенью 2. Программистам P4
  /// следует ограничивать аргументы значениями, обеспечивающими
  /// переносимость программы.

  Random(T min, T max);
  T read();

  /*
  @ControlPlaneAPI
  {
    void reset();
    void setSeed(in T seed);
  }
  */
}
// END:Random_extern

// BEGIN:ActionProfile_extern
extern ActionProfile {
  /// Construct an action profile of 'size' entries
  ActionProfile(bit<32> size);

  /*
  @ControlPlaneAPI
  {
     entry_handle add_member    (action_ref, action_data);
     void         delete_member (entry_handle);
     entry_handle modify_member (entry_handle, action_ref, action_data);
  }
  */
}
// END:ActionProfile_extern

// BEGIN:ActionSelector_extern
extern ActionSelector {
  /// Создает селектор действий с числом записей size.
  /// @param algo - алгоритм хэширования для выбора записи в группе;
  /// @param size - число записей в селекторе действий;
  /// @param outputWidth - размер ключа выбора.
  ActionSelector(PSA_HashAlgorithm_t algo, bit<32> size, bit<32> outputWidth);

  /*
  @ControlPlaneAPI
  {
     entry_handle add_member        (action_ref, action_data);
     void         delete_member     (entry_handle);
     entry_handle modify_member     (entry_handle, action_ref, action_data);
     group_handle create_group      ();
     void         delete_group      (group_handle);
     void         add_to_group      (group_handle, entry_handle);
     void         delete_from_group (group_handle, entry_handle);
  }
  */
}
// END:ActionSelector_extern

// BEGIN:Digest_extern
extern Digest<T> {
  Digest();                       /// Определяет поток сообщений (digest) для 
                                  /// плоскости управления.
  void pack(in T data);           /// Передает данные в поток сообщений.
  /*
  @ControlPlaneAPI
  {
  T data;                           /// Если T является списком, плоскость управления
                                    /// создает структуру (struct).
  int unpack(T& data);              /// Неупакованные данные находятся в T&, int 
                                    /// int возвращает код состояния.
  }
  */
}
// END:Digest_extern

// BEGIN:Programmable_blocks
parser IngressParser<H, M, RESUBM, RECIRCM>(
    packet_in buffer,
    out H parsed_hdr,
    inout M user_meta,
    in psa_ingress_parser_input_metadata_t istd,
    in RESUBM resubmit_meta,
    in RECIRCM recirculate_meta);

control Ingress<H, M>(
    inout H hdr, inout M user_meta,
    in    psa_ingress_input_metadata_t  istd,
    inout psa_ingress_output_metadata_t ostd);

control IngressDeparser<H, M, CI2EM, RESUBM, NM>(
    packet_out buffer,
    out CI2EM clone_i2e_meta,
    out RESUBM resubmit_meta,
    out NM normal_meta,
    inout H hdr,
    in M meta,
    in psa_ingress_output_metadata_t istd);

parser EgressParser<H, M, NM, CI2EM, CE2EM>(
    packet_in buffer,
    out H parsed_hdr,
    inout M user_meta,
    in psa_egress_parser_input_metadata_t istd,
    in NM normal_meta,
    in CI2EM clone_i2e_meta,
    in CE2EM clone_e2e_meta);

control Egress<H, M>(
    inout H hdr, inout M user_meta,
    in    psa_egress_input_metadata_t  istd,
    inout psa_egress_output_metadata_t ostd);

control EgressDeparser<H, M, CE2EM, RECIRCM>(
    packet_out buffer,
    out CE2EM clone_e2e_meta,
    out RECIRCM recirculate_meta,
    inout H hdr,
    in M meta,
    in psa_egress_output_metadata_t istd,
    in psa_egress_deparser_input_metadata_t edstd);

package IngressPipeline<IH, IM, NM, CI2EM, RESUBM, RECIRCM>(
    IngressParser<IH, IM, RESUBM, RECIRCM> ip,
    Ingress<IH, IM> ig,
    IngressDeparser<IH, IM, CI2EM, RESUBM, NM> id);

package EgressPipeline<EH, EM, NM, CI2EM, CE2EM, RECIRCM>(
    EgressParser<EH, EM, NM, CI2EM, CE2EM> ep,
    Egress<EH, EM> eg,
    EgressDeparser<EH, EM, CE2EM, RECIRCM> ed);

package PSA_Switch<IH, IM, EH, EM, NM, CI2EM, CE2EM, RESUBM, RECIRCM> (
    IngressPipeline<IH, IM, NM, CI2EM, RESUBM, RECIRCM> ingress,
    PacketReplicationEngine pre,
    EgressPipeline<EH, EM, NM, CI2EM, CE2EM, RECIRCM> egress,
    BufferingQueueingEngine bqe);

// END:Programmable_blocks

#endif  /* _PORTABLE_SWITCH_ARCHITECTURE_P4_ */

#endif   // __PSA_P4__

Николай Малых

nmalykh@protocols.ru




Архитектура uBPF

PDF

Приведенный ниже включаемый файл (include) с переведенными на русский язык комментариями содержит определения пакета ubpf в одноименной модели. Файл ubpf.p4 размещается в каталоге p4include пакета p4c и доступен по ссылке.

/*
Copyright 2019 Orange

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#ifndef _UBPF_MODEL_P4_
#define _UBPF_MODEL_P4_

#include <core.p4>

/*
 * Платформа uBPF в настоящее время позволяет пропускать (pass) или отбрасывать 
 * (drop) пакеты. По умолчанию пакеты пропускаются. Можно использовать 
 * внешнюю функцию mark_to_drop(), чтобы пометить пакет для отбрасывания. 
 * Функция mark_to_drop() меняет лишь состояние, скрытое от  пользовательской
 * программы P4 и вызывать ее следует лишь из элемента управления pipe.
 */
extern void mark_to_drop();

/*
 * Платформа uBPF в настоящее время позволяет пропускать (pass) или отбрасывать 
 * (drop) пакеты. По умолчанию пакеты пропускаются. Можно использовать 
 * внешнюю функцию mark_to_pass() чтобы пометить пакет для пропускания (это
 * отменяет предшествующее действие mark_to_drop()). Функция mark_to_drop()
 * меняет лишь состояние, скрытое от  пользовательской программы P4 
 * и вызывать ее следует лишь из элемента управления pipe.
 */
extern void mark_to_pass();


extern Register<T, S> {
  /***
   * Объект Register создается вызовом его конструктора. При вызове нужно
   * указать размер Register, определяющий максимальное число записей в
   * регистре. После создания объекта Register его можно применять в
   * действиях и блоках apply. При создании Register не инициализируется.
   */
  Register(bit<32> size);

  /***
   * Метод read() считывает состояние (T) из массива регистров по заданному
   * индексу S и возвращает значение в параметре result.
   *
   * @param index Индекс элемента массива регистров для считывания. Обычно
   *              из диапазона [0, size-1].
   * @return Возвращает результат типа T (в настоящее время только bit<W>).
   *         Если индекс находится в диапазоне, result получает значение 
   *         элемента массива регистров. При index >= size результат 
   *         становится неопределенным и его следует игнорировать.
   */
  T read  (in S index);

  void write (in S index, in T value);
}

/*
 * Функция возвращает метку текущего времени в наносекундах.
 */
extern bit<48> ubpf_time_get_ns();

enum HashAlgorithm {
    lookup3
}

/***
 * Рассчитывает хэш-функцию от значения, заданного параметром data. В силу 
 * ограничений uBPF максимальный размер data составляет bit<64>.
 *
 * Отметим, что типы всех параметров могут быть одинаковыми или разными.
 * Во втором случае их битовые размеры могут различаться.
 *
 * Результат всегда имеет размер bit<32>.
 *
 * @param D    Должен иметь тип tuple, состоящий из битовых полей (type bit<W>, int<W>) 
 *             или varbit. Максимальный размер D составляет 64 бита (ограничение uBPF).
 */
extern void hash<D>(out bit<32> result, in HashAlgorithm algo, in D data);

/***
 * Расчет контрольной суммы с помощью инкрементального обновления (RFC 1624).
 * Функция реализует расчет контрольной суммы для 16-битовых полей.
 */
extern bit<16> csum_replace2(in bit<16> csum,  // Текущая контрольная сумма.
                             in bit<16> old,   // Старое значение поля.
                             in bit<16> new);

/***
 * Расчет контрольной суммы с помощью инкрементального обновления (RFC 1624).
 * Функция реализует расчет контрольной суммы для 32-битовых полей.
 */
extern bit<16> csum_replace4(in bit<16> csum,
                             in bit<32> old,
                             in bit<32> new);

/*
 * Архитектура
 *
 * Параметр M должен иметь тип struct.
 *
 * Параметр H должен иметь тип struct, а каждый элемент структуры должен
 * быть заголовком, стеком или объединением заголовков (header_union).
 */

parser parse<H, M>(packet_in packet, out H headers, inout M meta);
control pipeline<H, M>(inout H headers, inout M meta);

/*
 * В теле сборщика (deparser) допускается лишь вызов метода packet_out.emit().
 */
@deparser
control deparser<H>(packet_out b, in H headers);

package ubpf<H, M>(parse<H, M> prs,
                pipeline<H, M> p,
                deparser<H> dprs);

#endif /* _UBPF_MODEL_P4_ */

Николай Малых

nmalykh@protocols.ru




Базовая библиотека P4 — core.p4

PDF

В спецификации языка P416 часть базовых функций была вынесена в отдельную библиотеку core.p4, содержащую базовые определения и функции языка и используемую в качестве включаемого файла (директива #include) программами P4. Ниже представлен вариант этого файла с переведенными на русский язык комментариями. Исходный файл доступен в каталоге p4includes пакета p4c или по ссылке.

/*
Copyright 2013-present Barefoot Networks, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Базовая библиотека P4-16, где объявлены некоторые встроенные элементы языка P4 для использования в программах P4 */

#ifndef _CORE_P4_
#define _CORE_P4_

/// Стандартные коды ошибок. Пользователи могут добавлять свои коды.
error {
    NoError,           /// Нет ошибок.
    PacketTooShort,    /// В пакете недостаточно битов для извлечения.
    NoMatch,           /// В выражении select не найдено соответствия.
    StackOutOfBounds,  /// Ссылка на недействительный элемент стека заголовков.
    HeaderTooShort,    /// Извлекается слишком много битов заголовка в поле varbit.
    ParserTimeout,     /// Превышено время выполнения кода синтаксического анализатора.
    ParserInvalidArgument  /// Операция анализатора была вызвана с неподдерживаемым
                           /// реализацией значением.
}

extern packet_in {
    /// Считывает заголовок пакета в заголовок фиксированного размера и перемещает указатель.
    /// Может приводить к ошибке PacketTooShort или StackOutOfBounds.
    /// Параметр @T должен иметь тип заголовка фиксированного размера.
    void extract<T>(out T hdr);
    /// Считывает биты из заголовка пакета в заголовок @variableSizeHeader с переменным размером
    /// и перемещает указатель.
    /// Параметр @T должен быть заголовком, содержащим в точности 1 поле 1.
    /// Может приводить к ошибке PacketTooShort, StackOutOfBounds или HeaderTooShort.
    void extract<T>(out T variableSizeHeader,
                    in bit<32> variableFieldSizeInBits);
    /// Считывает биты из пакета без перемещения указателя.
    /// Параметр @returns содержит прочитанные из пакета биты.
    /// T может быть произвольным типом фиксированного размера.
    T lookahead<T>();
    /// Перемещает указатель на заданное число битов.
    void advance(in bit<32> sizeInBits);
    /// Переменная @return содержит размер пакета в байтах. Метод может не поддерживаться архитектурой.
    bit<32> length();
}

extern packet_out {
    /// Записывает заголовок @hdr в выходной пакет, перемещая указатель.
    /// @T может иметь тип заголовка, стека или объединения заголовков, а также структурой с 
    /// такими полями.
    void emit<T>(in T hdr);
}

// В будущих версиях следует исключить приведенное ниже из файла, преобразовав в built-in

/// Проверяет предикат @check в синтаксическом анализаторе. При значении true не делает ничего,
/// в противном случае устанавливает ошибку анализатора в @toSignal и переходит в состояние reject.
extern void verify(in bool check, in error toSignal);

/// Встроенная пустая операция.
@noWarn("unused")
action NoAction() {}

/// Стандартные типы сопоставления для ключей таблиц.
/// Отдельные типы могут не поддерживаться архитектурой.
/// Архитектура может добавлять типы сопоставления.
match_kind {
    /// Точное совпадение битов
    exact,
    /// Троичное сопоставление с использованием маски.
    ternary,
    /// Наибольший совпадающий префикс.
    lpm
}

#endif  /* _CORE_P4_ */

Николай Малых

nmalykh@protocols.ru




Архитектура v1model

PDF

Приведенный ниже включаемый файл (include) с переведенными на русский язык комментариями содержит определения пакета V1Switch для архитектуры v1model и платформы BMV2. Файл v1model.p4 размещается в каталоге p4include пакета p4c (https://github.com/p4lang/p4c/blob/master/p4include/v1model.p4).

Этот файл включается в программы P4, работающие с прототипом коммутатора simple_switch на основе BMV2, включая тестовые примеры из пакета p4c (https://github.com/p4lang/p4c/tree/master/testdata).

/*
Copyright 2013-present Barefoot Networks, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Объявление P4-16 для модели коммутатора P4 v1.0 */

/* Примечание 1. Более подробное описание архитектуры v1model доступно
 * по приведенной ниже ссылке.
 *
 * https://github.com/p4lang/behavioral-model/blob/master/docs/simple_switch.md1 
 *
 * Примечание 2. В начале 2019 г. в рабочей группе P4 несколько раз
 * обсуждались способы вызова операций resubmit, recirculate и clone3
 * из соответствующих элементов управления (control), но значениями 
 * сохраняемых полей будут значения на момент завершения вызова
 * элемента управления. Так эти операции определены в P4_14. См.
 * https://github.com/p4lang/behavioral-model/blob/master/docs/simple_switch.md#restrictions-on-recirculate-resubmit-and-clone-operations1 
 *
 * Примечание 3. Имеются реализации P4_14, где вызов операции
 * generate_digest в field_list приводит к созданию сообщения для
 * плоскости управления, содержащего значения этих полей при завершении
 * работы элемента управления ingress, которые могут отличаться от
 * значений этих же полей в момент вызова из программы операции
 * generate_digest, если значения этих полей меняются при вызове 
 * элемента управления P4_14 ingress.
 *
 * Реализации P4_16 с моделью v1model всегда следует создавать 
 * сообщения digest, содержащие значения указанных полей на момент вызова
 * внешней функции digest. Программа P4_14 с описанным выше поведением,
 * скомпилированная с использованием p4c, может вести себя иначе.
 */

#ifndef _V1_MODEL_P4_
#define _V1_MODEL_P4_

#include "core.p4"

#ifndef V1MODEL_VERSION
#define V1MODEL_VERSION 20180101
#endif

match_kind {
    range,
    // Точное или шаблонное (соответствует любому) совпадение.
    optional,
    // Служит для реализации dynamic_action_selection.
    selector
}

const bit<32> __v1model_version = V1MODEL_VERSION;

#if V1MODEL_VERSION >= 20200408
typedef bit<9>  PortId_t;       // Не должно иметь постоянный размер?
#endif

@metadata @name("standard_metadata")
struct standard_metadata_t {
#if V1MODEL_VERSION >= 20200408
    PortId_t    ingress_port;
    PortId_t    egress_spec;
    PortId_t    egress_port;
#else
    bit<9>      ingress_port;
    bit<9>      egress_spec;
    bit<9>      egress_port;
#endif
    bit<32>     instance_type;
    bit<32>     packet_length;
    //
    // @alias служит для создания раздела field_alias в файле BMV2 JSON.
    // Псевдоним поля создает отображение имени метаданных в программе P4
    // на внутреннее имя метаданных в модели поведения (bmv2). Здесь это
    // служит для раскрытия всех метаданных, поддерживаемых simple_switch, 
    // для пользователя через standard_metadata_t.
    //
    // «Сглаживающие» поля из bmv2-ss для запроса метаданных.
    @alias("queueing_metadata.enq_timestamp")
    bit<32> enq_timestamp;
    @alias("queueing_metadata.enq_qdepth")
    bit<19> enq_qdepth;
    @alias("queueing_metadata.deq_timedelta")
    bit<32> deq_timedelta;
    /// Глубина очереди в момент извлечения пакета из нее.
    @alias("queueing_metadata.deq_qdepth")
    bit<19> deq_qdepth;

    // Внутренние метаданные
    @alias("intrinsic_metadata.ingress_global_timestamp")
    bit<48> ingress_global_timestamp;
    @alias("intrinsic_metadata.egress_global_timestamp")
    bit<48> egress_global_timestamp;
    /// Идентификатор multicast-группы (ключ для таблицы репликации mcast)
    @alias("intrinsic_metadata.mcast_grp")
    bit<16> mcast_grp;
    /// Идентификатор репликации для multicast
    @alias("intrinsic_metadata.egress_rid")
    bit<16> egress_rid;
    /// Указывает отрицательный результат verify_checksum().
    /// 1 при ошибке контрольной суммы, иначе 0.
    bit<1>  checksum_error;
    /// Ошибка при синтаксическом анализе.
    error parser_error;
    /// Задание приоритета для пакета.
    @alias("intrinsic_metadata.priority")
    bit<3> priority;
}

enum CounterType {
    packets,
    bytes,
    packets_and_bytes
}

enum MeterType {
    packets,
    bytes
}

extern counter
#if V1MODEL_VERSION >= 20200408
<I>
#endif
{
    /***
     * Объект counter (счетчик) создается вызовом конструктора. При 
     * этом создается массив состояний счетчика с набором состояний,
     * заданным параметром size. Индексы массива имеют значения  
     * [0, size-1].
     *
     * Нужно указать, будут ли учитываться только пакеты 
     * (CounterType.packets), только байты (CounterType.bytes) или
     * то и другое (CounterType.packets_and_bytes).
     *
     * Счетчики могут обновляться из программы P4, но для чтения 
     * доступны лишь плоскости управления. Если требуется чтение и 
     * запись из программы P4, следует использовать регистры.
     */
    counter(bit<32> size, CounterType type);
    /* FIXME - Аргумент size должен иметь тип int, но это нарушает
     * проверку типов
     */
 
    /***
     * count() вызывает для указанного индексом состояния считывание,
     * изменение и запись обратно в виде неделимого (atomic) блока
     * относительно обработки других пакетов. Обновление счетчиков 
     * пакетов, байтов или обоих зависит от CounterType экземпляра
     * счетчика при вызове для него конструктора.
     *
     * @param index Индекс обновляемого состояния счетчика в массиве.
     *              Обычно это значение из диапазона [0, size-1].
     *              Если index >= size, состояние счетчика не меняется.
     */
#if V1MODEL_VERSION >= 20200408
    void count(in I index);
#else
    void count(in bit<32> index);
#endif
}

extern direct_counter {
    /***
     * Объект direct_counter создается вызовом его конструктора. Нужно
     * указать подсчет только пакетов (CounterType.packets), только
     * байтов (CounterType.bytes) или обоих (CounterType.packets_and_bytes).
     * После создания объекта его можно связать с одной таблицей, добавляя
     * в ее определение указанное ниже свойство
     *     counters = <object_name>;
     *
     * Счетчики могут обновляться из программы P4, но для чтения 
     * доступны лишь плоскости управления. Если требуется чтение и 
     * запись из программы P4, следует использовать регистры.
     */
    direct_counter(CounterType type);
    /***
     * Метод count() реально не требуется в архитектуре v1model. 
     * Это связано с тем, что после привязки объекта direct_counter к
     * таблице, как описано в документации конструктора direct_counter,
     * при каждом обращении к таблице с возвратом подходящей записи
     * состояние счетчика, связанного с этой записью, считывается, 
     * изменяется и записывается обратно неделимым блоком относительно 
     * обработки других пакетов независимо от вызова метода count() из
     * тела данного действия.
     */
    void count();
}

#define V1MODEL_METER_COLOR_GREEN  0
#define V1MODEL_METER_COLOR_YELLOW 1
#define V1MODEL_METER_COLOR_RED    2

extern meter
#if V1MODEL_VERSION >= 20200408
<I>
#endif
{
    /***
     * Объект meter создается вызовом его конструктора. При этом 
     * создается массив состояний, число которых определяется параметром
     * size. Индексы массива лежат в диапазоне [0, size-1]. Например,
     * при наличии в системе 128 «потоков» с номерами от 0 до 127 и 
     * желании независимо измерять каждый из них, можно создать 
     * измеритель с size=128.
     *
     * Нужно указать для измерителя учет пакетов независимо от размера
     * (MeterType.packets) или числа байтов в пакетах (MeterType.bytes).
     */
    meter(bit<32> size, MeterType type);
    /* FIXME - Аргумент size должен иметь тип int, но это нарушает
     * проверку типов
     */

    /***
     * execute_meter() вызывает для указанного состояния считывание, 
     * изменение и запись обратно в виде неделимого блока относительно
     * обработки других пакетов и представление одним из цветов 
     * (green, yellow, red) в параметре out.
     *
     * @param index Индекс обновляемого состояния в массиве. Обычно из
     *              диапазона [0, size-1]. Если index >= size, состояние
     *              не обновляется.
     * @param result Тип T должен быть bit<W> с W >= 2. Если индекс
     *              находится в диапазоне, будет указано значение 0 для
     *              GREEN, 1 - для YELLOW и 2 - для RED (см. RFC 2697 и 
     *              RFC 2698). Если индекс выходит за пределы диапазона,
     *              результат становится неопределенным и его следует
     *              игнорировать вызывающему.
     */
#if V1MODEL_VERSION >= 20200408
    void execute_meter<T>(in I index, out T result);
#else
    void execute_meter<T>(in bit<32> index, out T result);
#endif
}

extern direct_meter<T> {
    /***
     * Объект direct_meter создается вызовом его конструктора. Нужно
     * указать учет пакетов независимо от размера (MeterType.packets)
     * или числа байтов в пакетах (MeterType.bytes). Созданный объект
     * можно связать с одной таблицей, добавляя в ее определение
     * приведенное ниже свойство
     *     meters = <object_name>;
     */
    direct_meter(MeterType type);
    /***
     * После привязки объекта direct_meter к таблице, как описано в 
     * документации конструктора direct_meter, при каждом нахождении
     * в таблице совпадающей записи значение связанного с этой записью
     * измерителя считывается, обновляется и записывается обратно в 
     * форме неделимого блока относительно обработки других пакетов
     * независимо от вызова метода read() из тела данного действия.
     *
     * Метод read() можно вызывать лишь внутри действия, выполняемого в
     * результате совпадения записи в таблице, с которой связан объект 
     * direct_meter. Результатом вызова read() будет численное 
     * представление цвета (green, yellow, red) в параметре out.
     *
     * @param result Тип T должен быть bit<W> с W >= 2. Указывается 
     *              значение 0 для GREEN, 1 - для YELLOW и 2 - для RED 
     *              (см. RFC 2697 и RFC 2698). 
     */
    void read(out T result);
}

#if V1MODEL_VERSION >= 20200408
extern register<T, I>
#else
extern register<T>
#endif
{
    /***
     * Объект register создается вызовом его конструктора. При этом
     * создается массив из size идентичных элементов типа T с индексами
     * из диапазона [0, size-1]. Например, вызов
     *     register<bit<32>>(512) my_reg;
     * выделит место для 512 значений типа bit<32>.
     */
    register(bit<32> size);
    /* FIXME - Аргумент size должен иметь тип int, но это нарушает
     * проверку типов
     */
    /***
     * Метод read() считывает из массива регистров указанное индексом 
     * состояние и возвращает его значение в параметре result.
     *
     * @param index Индекс элемента в массиве регистров для чтения.
     *              Обычно лежит в диапазоне [0, size-1].
     * @param result Поддерживаются лишь типы bit<W>. Когда индекс
     *              находится в диапазоне, result получает значение
     *              указанного элемента массива. Если index >= size, 
     *              результат будет неопределенным и его следует 
     *              игнорировать вызывающему.
     */
    @noSideEffects
#if V1MODEL_VERSION >= 20200408
    void read(out T result, in I index);
#else
    void read(out T result, in bit<32> index);
#endif
    /***
     * Метод write() записывает значение параметра value в массив 
     * регистров по указанному индексу.
     *
     * Если нужно выполнить read(), а затем write() для одного
     * элемента массива регистров и хочется сделать блок 
     * read-modify-write неделимым относительно обработки других пакетов
     * (параллельное выполнение в архитектуре v1model) требуется 
     * указать блок P4_16 с аннотацией @atomic (см. спецификацию P4_16).
     *
     * @param index Индекс элмента массива регистров для записи. Обычно
     *              из диапазона [0, size-1]. Если index >= size, состояние
     *              регистров не изменяется.
     * @param value Поддерживаются лишь типы bit<W>. Когда индекс
     *              находится в диапазоне, значение value записывается
     *              в указанный индексом элемент массива регистров.
     */
#if V1MODEL_VERSION >= 20200408
    void write(in I index, in T value);
#else
    void write(in bit<32> index, in T value);
#endif
}

// Служит атрибутом реализации таблицы
extern action_profile {
    action_profile(bit<32> size);
}

/***
 * Генерирует случайное значение из диапазона [lo..hi] и записывает
 * его в параметр result. Значение result становится неопределенным,
 * если lo > hi.
 *
 * @param T          Должно иметь тип bit<W>
 */
extern void random<T>(out T result, in T lo, in T hi);

/***
 * Вызов digest вызывает передачу сообщения, содержащего значения,
 * указанные в параметре data, программе плоскости управления. Это
 * похоже на отправку клона пакета плоскости управления, но может
 * быть более эффективным за счет того, что сообщения обычно меньше
 * пакета и можно собрать несколько таких сообщений в блок для 
 * одновременной отправки программе плоскости управления.
 *
 * Значениями полей, передаваемых в сообщении плоскости управления, 
 * будут соответствующие значения в момент вызова digest, даже если
 * эти поля позднее будут изменены элементом управления ingress 
 * (см. Примечание 3).
 *
 * Вызов digest поддерживается лишь из элемента управления ingress. 
 * После вызова метода нельзя отказаться от производимого им эффекта.
 *
 * Если тип T является именованной структурой, имя служит для генерации 
 * API плоскости управления.
 *
 * Реализация архитектуры v1model в BMv2 игнорирует значение параметра
 * receiver.
 */
extern void digest<T>(in bit<32> receiver, in T data);

enum HashAlgorithm {
    crc32,
    crc32_custom,
    crc16,
    crc16_custom,
    random,
    identity,
    csum16,
    xor16
}

@deprecated("Please use mark_to_drop(standard_metadata) instead.")
extern void mark_to_drop();

/***
 * mark_to_drop(standard_metadata) является примитивом действия, меняющим 
 * standard_metadata.egress_spec на зависимое от реализации специальное
 * значение, что в некоторых случаях ведет к отбрасывают пакета в конце
 * входной или выходной обработки. Кстанавливается также значение 0 для
 * standard_metadata.mcast_grp. Любое из этих полей метаданных может быть
 * изменено кодом P4 после вызова mark_to_drop(), что может поменять для
 * пакета поведение на отличное от его отбрасывания (drop).
 *
 * В параграфе «Pseudocode for what happens at the end of ingress and 
 * egress processing» документа
 * https://github.com/p4lang/behavioral-model/blob/master/docs/simple_switch.md2 
 * описан относительный приоритет возможных действий применительно к пакету
 * по завершении входной и выходной обработки.
 */
@pure
extern void mark_to_drop(inout standard_metadata_t standard_metadata);

/***
 * Расчет хэш-функции для значения, указанного параметром data. Результат,
 * записываемый в параметр out с именем result, всегда будет находиться в
 * диапазоне [base, base+max-1], если max >= 1. При max=0 будет записано base.
 *
 * Отметим, что типы параметров могут быть одинаковыми или разными и битовые
 * размеры могут различаться.
 *
 * @param O   Должен иметь тип bit<W>
 * @param D   Должен иметь тип tuple, где все поля являются битовыми (bit<W> или int<W>) или varbit.
 * @param T   Должен иметь тип bit<W>
 * @param M   Должен иметь тип bit<W>
 */
@pure
extern void hash<O, T, D, M>(out O result, in HashAlgorithm algo, in T base, in D data, in M max);

extern action_selector {
    action_selector(HashAlgorithm algorithm, bit<32> size, bit<32> outputWidth);
}

enum CloneType {
    I2E,
    E2E
}

@deprecated("Please use verify_checksum/update_checksum instead.")
extern Checksum16 {
    Checksum16();
    bit<16> get<D>(in D data);
}

/***
 * Проверяет контрольную сумму представленных данных. При обнаружении
 * несоответствия поле standard_metadata checksum_error будет иметь 
 * в начале входной обработки значение 1.
 *
 * Вызов verify_checksum поддерживается лишь из элемента VerifyChecksum.
 *
 * @param T          Должен иметь тип tuple, все элементы которого имеют
 *                   тип bit<W>, int<W> или ,varbit<W>. Одщий размер полей
 *                   должен быть кратным выходному размеру.
 * @param O          Тип контрольной суммы (bit<X>).
 * @param condition  Значение false, задает совпадение при любом результате.
 * @param data       Данные для проверки контрольной суммы.
 * @param checksum   Ожидаемая контрольная сумма (должно быть l-value).
 * @param algo       Алгоритм расчета контрольной суммы (могут поддерживаться
 *                   не все алгоритмы), должен быть известной при компиляции
 *                   константой.
 */
extern void verify_checksum<T, O>(in bool condition, in T data, in O checksum, HashAlgorithm algo);

/***
 * Рассчитывает контрльную сумму представленных данных и записывает ее в 
 * параметр checksum.
 *
 * Вызов update_checksum поддерживается лишь из элементов управления 
 * ComputeChecksum.
 *
 * @param T          Должен иметь тип tuple, все элементы которого имеют
 *                   тип bit<W>, int<W> или ,varbit<W>. Одщий размер полей
 *                   должен быть кратным выходному размеру.
 * @param O          Тип контрольной суммы (bit<X>).
 * @param condition  При значении false параметр checksum не изменяется.
 * @param data       Данные для расчета контрольной суммы.
 * @param checksum   Контрольная сумма данных.
 * @param algo       Алгоритм расчета контрольной суммы (могут поддерживаться
 *                   не все алгоритмы), должен быть известной при компиляции
 *                   константой.
 */
@pure
extern void update_checksum<T, O>(in bool condition, in T data, inout O checksum, HashAlgorithm algo);

/***
 * verify_checksum_with_payload идентичен verify_checksum, но учитывает
 * при расчете контрольной суммы данные из пакета (все байты, которые не 
 * разбираются синтаксическим анализатором).
 *
 * Вызов verify_checksum_with_payload поддерживается лишь из элемента
 * управления VerifyChecksum.
 */
extern void verify_checksum_with_payload<T, O>(in bool condition, in T data, in O checksum, HashAlgorithm algo);

/**
 * update_checksum_with_payload идентичен update_checksum, но учитывает
 * при расчете контрольной суммы данные из пакета (все байты, которые не 
 * разбираются синтаксическим анализатором).
 *
 * Вызов update_checksum_with_payload поддерживается лишь из элемента
 * управления ComputeChecksum.
 */
@noSideEffects
extern void update_checksum_with_payload<T, O>(in bool condition, in T data, inout O checksum, HashAlgorithm algo);

/***
 * Вызов resubmit в процессе выполнения элемента ingress будет при 
 * некоторых документированных условиях приводить к повторному 
 * представлению пакета, т. е. пакет будет заново обработан анализатором
 * с тем же содержимым, которое было при предыдущей обработке. Различие
 * состоит лишь в значении поля standard_metadata instance_type и 
 * заданных пользователем полей метаданных, которые операция resubmit 
 * будет сохранять.
 *
 * Значения пользовательских метаданных, сохраняемых при повторном 
 * представлении пакетов, являются значениями в конце входной обработки,
 * а не значениями в момент вызова resubmit (см. Примечание 2).
 *
 * Вызов resubmit поддерживается лишь из элемента управления ingress. 
 * Отменить воздействие этого вызова невозможно. При неоднократном вызове
 * resubmit в одном процессе выполнения элемента ingress повторно 
 * представляется лишь один пакет и сохраняются данные лишь для последнего
 * вызова (см. Примечание 1).
 */
extern void resubmit<T>(in T data);

/***
 * Вызов recirculate в процессе выполнения элемента ingress будет при 
 * некоторых документированных условиях приводить к повторной обработке 
 * пакета, начиная с синтаксического анализа пакета, созданного сборщиком
 * (deparser). Рециркулирующие пакеты могут при входной обработке 
 * различаться с новыми пакетами значением поля standard_metadata
 * instance_type. Вызывающий может запросить сохранение некоторых 
 * полей пользовательских метаданных при рециркуляции пакета.
 *
 * Значения пользовательских метаданных, сохраняемых при рециркуляции 
 * пакетов, являются значениями в конце выходной обработки,
 * а не значениями в момент вызова recirculate (см. Примечание 2).
 *
 * Вызов recirculate поддерживается лишь из элемента управления egress. 
 * Отменить воздействие этого вызова невозможно. При неоднократном вызове
 * recirculate в одном процессе выполнения элемента egress рециркулирует 
 * лишь один пакет и сохраняются данные лишь для последнего
 * вызова (см. Примечание 1).
 */
extern void recirculate<T>(in T data);

/***
 * Операция clone почти идентична clone3 и отличается лишь тем, что
 * никогда не сохраняет поля пользовательских метаданных в клонах пакетов.
 * Она эквивалентна вызову clone3 с теми же параметрами type и session, но
 * с пустым параметром data.
 */
extern void clone(in CloneType type, in bit<32> session);

/***
 * Вызов clone3 при выполнении элемента управления ingress или egress
 * приводит к созданию клонов пакета (иногда называют отражением). 
 * Т. е. могут создаваться копии пакета, каждая из которых затем 
 * подвергается выходной обработке как независимый пакет. Для исходного
 * пакета продолжается обычная обработка независимо от клонов.
 *
 * Целочисленный параметр session содержит идентификатор сеанса клонирования
 * (иногда называется идентификатором сеанса отражения). Программы плоскости
 * управления должны настроить конфигурацию каждой сессии, которую планируется
 * использовать. Без этого клонирования происходить не будет. Обычно такая 
 * настройка включает задание плоскостью управления выходного порта, куда 
 * будет передаваться клон или списка пар (port, egress_rid), для которых 
 * следует создавать клоны подобно групповым пакетам.
 *
 * Клонированные пакеты можно различать по значениям поля 
 * standard_metadata instance_type.
 *
 * Вызывающий может запросить сохранение некоторых полей пользовательских 
 * метаданных исходного пакета при клонировании. Сохраняться будут значения
 * в конце входной или выходной обработки, а не в момент вызова clone3
 * (см. Примечание 2).
 *
 * При вызове clone3 во время входной обработки первым параметром должен быть
 * CloneType.I2E, для вызова при выходной обработке - CloneType.E2E.
 *
 * Отменить воздействие этого вызова невозможно. При неоднократном вызове
 * clone3 и/или clone в одном процессе входной или выходной обработки 
 * используется лишь последняя сессия клонирования и данные (см. Примечание 1).
 */
extern void clone3<T>(in CloneType type, in bit<32> session, in T data);

extern void truncate(in bit<32> length);

/***
 * Вызов assert с аргументом, имеющим значение true не дает эффекта, за 
 * исключением тех, которые может давать вычисление аргумента (см. ниже).
 * Если аргумент имеет значение false, поведение зависит от целевой платформы,
 * но цель заключается в записи или выводе информации об отказавшем операторе 
 * assert и возможно дополнительных данных об отказе.
 *
 * Например, на платформе simple_switch выполнение оператора assert с
 * аргументом false вызывает сообщение с именем файла и номером строки с
 * оператором assert, после чего процесс simple_switch завершается.
 *
 * Если была задана опция --ndebug в командной строке p4c при компиляции,
 * скомпилированная программа ведет себя как при отсутствии операторов 
 * assert в исходном коде.
 *
 * Настоятельно рекомендуется избегать выражений в качестве аргумента 
 * assert, поскольку это может приводить к побочным эффектам, например,
 * вызову внешнего метода или функции с побочным эффектом. Компилятор p4c 
 * позволяет это, не выдавая предупреждений. Это рекомендуется потому, что
 * программа будет вести себя так же, как при удалении операторов assert.
 */
extern void assert(in bool check);

/***
 * Для целей компиляции и запуска программ P4 на целевом устройстве
 * операторы assert и assume являются идентичными, включая использование
 * опции --ndebug компилятор p4c для их исключения. См. описание assert.
 *
 * Причиной использования assume как отдельной функции является допущение 
 * о ее специальной трактовке инструментами формальной проверки. Для 
 * некоторых таких инструментов цель состоит в попытке найти примеры 
 * пакетов и установленных записей таблиц, где условие оператора assert
 * имеет значение false.
 *
 * Предположим, что такой инструмент запускается для программы и примером 
 * служит пакет MPLS, где hdr.ethernet.etherType == 0x8847. При рассмотрении
 * примера видно, что условие assert имеет значение false. Однако планируется 
 * развертывание программы P4 в сети, где нет пакетов MPLS. Можно добавить
 * в программу P4 дополнительные условия для корректной обработки пакетов без
 * отказов assert, но проще сказать инструменту, что «такие экземпляры пакетов
 * не применимы и не нужно их показывать» путем добавления оператора
 *     assume(hdr.ethernet.etherType != 0x8847);
 * в подходящее место программы. Тогда формальный инструмент не будет показывать 
 * такие примеры, ограничиваясь лишь теми, которые дают условие true.
 *
 * Причина одинакового поведения операторов assume и assert при компиляции для
 * целевого устройства заключается в том, что значение false при оценке 
 * условия во время работы в сети может говорить об ошибочности допущения 
 * и необходимости его проверки.
 */
extern void assume(in bool check);

/*
 * Вывод заданного пользователем сообщения.
 * Например, log_msg("User defined message");
 * или log_msg("Value1 = {}, Value2 = {}",{value1, value2});
 */
extern void log_msg(string msg);
extern void log_msg<T>(string msg, in T data);

// Имя standard_metadata является зарезервированным.

/*
 * Архитектура.
 *
 * M должно иметь тип struct.
 *
 * H должно иметь тип struct, где каждый из элементов является заголовком,
 * стеком или объединением заголовков.
 */

parser Parser<H, M>(packet_in b,
                    out H parsedHdr,
                    inout M meta,
                    inout standard_metadata_t standard_metadata);

/*
 * В теле элемента управления VerifyChecksum допускаются лишь операторы 
 * блока, вызовы методов verify_checksum и verify_checksum_with_payload, 
 * а также операторы return.
 */
control VerifyChecksum<H, M>(inout H hdr,
                             inout M meta);
@pipeline
control Ingress<H, M>(inout H hdr,
                      inout M meta,
                      inout standard_metadata_t standard_metadata);
@pipeline
control Egress<H, M>(inout H hdr,
                     inout M meta,
                     inout standard_metadata_t standard_metadata);

/*
 * В теле элемента управления ComputeChecksum допускаются лишь операторы 
 * блока, вызовы методов update_checksum и update_checksum_with_payload, 
 * а также операторы return.
 */
control ComputeChecksum<H, M>(inout H hdr,
                              inout M meta);

/*
 * В теле элемента управления Deparser допускается лишь метод packet_out.emit().
 */
@deparser
control Deparser<H>(packet_out b, in H hdr);

package V1Switch<H, M>(Parser<H, M> p,
                       VerifyChecksum<H, M> vr,
                       Ingress<H, M> ig,
                       Egress<H, M> eg,
                       ComputeChecksum<H, M> ck,
                       Deparser<H> dep
                       );

#endif  /* _V1_MODEL_P4_ */

Николай Малых

nmalykh@protocols.ru

1Имеется перевод документа на русский язык.

2Параграф «Псевдокод для завершения входной и выходной обработки» в переводе.




Параметры simple_switch

PDF

Синтаксис

simple_switch [опции] <путь к JSON-файлу конфигурации коммутатора>

Опции

-h [ —help ]

Выводит справочную информацию и на этом завершает работу.

-i [ —interface ] arg <port-num>@<interface-name>

Привязывает сетевой интерфейс <interface-name> в качестве порта с номером <port-num> при запуске программы. Опция может использоваться в команде многократно.

—pcap [=arg(=.)]

Задает создание файлов pcap для интерфейсов коммутатора. Необязательный аргумент позволяет указать каталог для сохранения файлов pcap, которые по умолчанию записываются в текущий каталог.

—use-files arg

Считывает пакеты из файлов или записывает в файлы (интерфейсу X соответствуют файлы X_in.pcap и X_out.pcap). Аргумент задает время ожидания (в секундах) перед началом обработки файлов с пакетами.

—packet-in arg

Включает прием пакетов на этом (nanomsg) сокете. В этом случае опция —interface будет игнорироваться.

—thrift-port arg

Порт TCP на котором будет работать сервер интерфейса управления Thrift.

—device-id arg

Идентификатор, указывающий устройство в сообщениях IPC. По умолчанию 0.

—nanolog arg

Сокет IPC для использования с журналами nanomsg pub/sub. По умолчанию журналы nanomsg не ведутся.

—log-console

Включает вывод журнальной информации на устройство stdout.

—log-file arg

Включает вывод журнала в указанный файл.

-L [ —log-level ] arg

Задает уровень детализации при выводе журнала работы и может принимать значение trace, debug, info, warn, error или off. По умолчанию используется уровень trace.

—log-flush

При использовании с опцией —log-file задает сброс журнала на диск после каждого сообщения.

—notifications-addr arg

Указывает адрес nanomsg для уведомлений (обучение, старения и т. п.), по умолчанию ipc:///tmp/bmv2-<device-id>-notifications.ipc

—restore-state arg

Задает восстановление состояния из файла.

—dump-packet-data arg

Задает число байтов для дампа принимаемых и передаваемых пакетов. Дамп выводится с уровнем info, поэтому следует убедиться, что при выводе этот уровень не исключается. По умолчанию установлено значение 0 и не выводится ничего.

-v [ —version ]

Выводит информацию о версии программы.

—json-version

Выводит максимальную поддерживаемую версию bmv2 JSON в формате <major>.<minor>. Должны поддерживаться все версии bmv2 JSON с совпадающим значением <major>.

—no-p4

Позволяет запустить коммутатор без файла конфигурации.

—max-port-count arg (=512)

Максимальное число интерфейсов, которые можно привязать к коммутатору. Это не верхняя граница номера порта, который может быть произвольным. В зависимости от платформы максимальное значение может не применяться.

Платформа имеет также свой анализатор команд и нужно отделять опции платформы от опций bmv2 двумя дефисами (—).

Опции платформы

—load-modules arg

Загружает указанные через запятую файлы .so как модули. Это полезно при использовании динамических библиотек с реализациями типа extern.

—enable-swap

Включает файл подкачки JSON в процессе работы.

—drop-port arg

Задает номер порта drop (по умолчанию 511).




Компилятор P4C (драйвер компиляции)

PDF

Синтаксис

p4c [-h] [-V] [-v] [-###] [-Xpreprocessor <arg>] [-Xp4c <arg>] 
    [-Xassembler <arg>] [-Xlinker <arg>] [-b TARGET] [-a ARCH] [-c] 
    [-D PREPROCESSOR_DEFINES] [-E] [-e] [-g] [-I SEARCH_PATH] [-o PATH] 
    [--p4runtime-file P4RUNTIME_FILE] 
    [--p4runtime-files P4RUNTIME_FILES] 
    [--p4runtime-format {binary,json,text}] [--help-pragmas] 
    [--help-targets] [--disable-annotations DISABLED_ANNOS] [-S] 
    [--std {p4-14,p4_14,p4-16,p4_16}] [--ndebug] 
    [source_file]

Позиционные аргументы source_file обязательны (хотя бы один) и указывают имена файлов P4 для компиляции.

Опции

Остальные аргументы команды являются необязательными (опции) и служат для управления компиляцией исходного кода P4. Многие опции доступны в краткой и длинной форме (с префиксом —)

-h, —help

Выводит справку о параметрах команды и завершает работу.

-V, —version

Выводит номер версии программы и завершает работу.

-v, —debug

Задает подробный вывод программы.

-###, —test-only

Выводит команды на консоль без из реального выполнения.

-Xpreprocessor <arg>

Задает передачу аргумента <arg> препроцессору компилятора.

-Xp4c <arg>

Задает аргумент, передаваемый компилятору.

-Xassembler <arg>

Задает передачу аргумента <arg> ассемблеру.

-Xlinker <arg>

Задает передачу аргумента <arg> компоновщику.

-b TARGET, —target TARGET

Задает целевое устройство (платформу).

-a ARCH, —arch ARCH

Задает целевую архитектуру.

-c, —compile

Задает выполнение лишь этапов предварительной обработки, компиляции и сборки.

-D PREPROCESSOR_DEFINES

Определяет макрос, используемый препроцессором.

-E

Задает выполнение только препроцессора.

-e

Задает пропуск препроцессора.

-g

Задает генерацию отладочной информации.

-I SEARCH_PATH

Задает каталог для поиска включаемых файлов.

-o PATH, —output PATH

Задает каталог для записи вывода.

—p4runtime-file P4RUNTIME_FILE

Задает вывод описания P4Runtime (API плоскости управления) в указанный файл. Опция устарела и взамен следует использовать —p4runtime-files.

—p4runtime-files P4RUNTIME_FILES

Задает вывод описания P4Runtime (P4Info, API плоскости управления) в указанные через запятые файлы. Формат файлов задают указанные расширения имен (суффиксы) .txt, .json, .bin.

—p4runtime-format {binary,json,text}

Задает выходной формат для описания P4Runtime API (по умолчанию .bin). Опция устарела и взамен следует использовать —p4runtime-files.

—help-pragmas, —pragma-help, —pragmas-help, —help-annotations, —annotation-help, —annotations-help

Выводит документацию поддерживаемых annotation и pragma, после чего завершает работу.

—help-targets, —target-help, —targets-help

Выводит связанные с целевой платформой опции команд.

—disable-annotations DISABLED_ANNOS, —disable-annotation DISABLED_ANNOS …

Задает список разделенных запятыми аннотаций, которые компилятору следует игнорировать.

-S

Задает выполнение лишь этапов предварительной обработки и компиляции.

—std {p4-14,p4_14,p4-16,p4_16}, -x {p4-14,p4_14,p4-16,p4_16}

Указывает вариант языка во входных файлах.

—ndebug

Задает компиляцию программы без отладочной информации.

Николай Малых

nmalykh@protocols.ru




Промежуточные представления компилятора P4C

Оригинал

PDF

Введение

В этом документе рассматривается устройство компилятора P416, описаны разные классы и структуры данных, применяемые для компиляции, а также сам процесс компиляции. Компилятор рассчитан на «традиционную» модель работы, преобразующую программу P4 во внутреннее представление, выполняющую несколько проходов с этим представлением, дающих в результате промежуточное представление (IR1), которое более или менее напрямую отображается в код для целевой платформы. По ходу дела набор классов IR, представленных в коде, может меняться, при этом некоторые конструкции будут присутствовать лишь в начальном представлении после синтаксического анализатора, а другие будут добавляться в процессе компиляции.

Новизна заключается в возможности всегда вернуться к более ранней версии IR (обратное прохождение) или использовать множество потоков при работе с IR, что позволяет реализовать разные стратегии при поиске окончательного результата. Поддержка ветвления и возврата является центральным моментом IR и преобразования. Задачей IR является представление программы в виде дерева или DAG2 с одним корневым узлом и остальными узлами, указывающими лишь своих потомков. Преобразования выполняются путем создания новых узлов IR, являющихся «мелкими» копиями имеющихся узлов, и последующего создания для них новых родительских узлов вплоть до корня IR. Внутреннее представления реализовано на C++ в форме одной иерархии наследования объектов с основным базовым узлом типа Node, используемым всеми субклассами. Этот базовый класс Node поддерживает функциональность, требуемую для преобразований — клонирование, проверку эквивалентности, диспетчеризацию субклассов.

Представление IR должно быть расширяемым, что позволяет добавлять новые классы в иерархию по мере добавления поддержки новых платформ. Исходное представление IR создаваемое синтаксическим анализатором, содержит конструкции, относящиеся лишь к исходному коду P4, и дле него будут выполняться преобразования frontend-компилятором для перевода в более простую и каноническую форму.

IR может быть деревом или DAG, но разрешение ссылок «вперед-назад» (back/up) в IR (циклах) сводит на нет большинство преимуществ неизменного IR. Если циклы (например, узлы-листья для имен напрямую ссылающиеся на именованный объект) разрешены, практически любое преобразование будет приводить к клонированию всего графа IR, поскольку изменение листа включает клонирование содержащего его объекта, а затем (рекурсивно) любого объекта, указывающего на этот объект.

Проходы Visitor и Transform

Компилятор организован в форме серии проходов Visitor и Transform. Базовые классы Visitor и Transform упрощают определение новых проходов и новому преобразованию требуется лишь указать нужные типы IR, а прочие можно игнорировать. Проход Visitor (постоянный) посещает каждый узел в дереве IR м собирает информацию о дереве, не меняя ее, а проход Transform посещает каждый узел и может менять его или заменять иным узлом.

    /* псевдокод базового преобразования */
    visit(node, context=ROOT, visited={}) {
        if (node in visited) {
                if (visited[node] == INVALID)
                        throw loop_detected
                if (VisitDagOnce)
                        return visited[node] }
        visited[node] = INVALID
        copy = shallow_clone(node)
        if (VisitDagOnce)
                forward_children(copy, visited)
        copy = preorder(copy, context) // переопределен в субклассе Transform
        visit_children(copy, {copy, context}, visited)
        copy = postorder(copy, context) // переопределен в субклассе Transform
        if (*copy == *node) {
                visited[node] = node
                return node
        } else {
                visited[node] = copy
                return copy }
    }
    forward_children(node, visited) {
        for (child in children(node))
                if (child in visited)
                        child = visited[child]
    }
    visit_children(node, context, visited) {
        for (child in children(node))
                child = visit(child, context, visited)
    }

В проходе Transform разрешено свободно изменять или полностью заменять узел в своих программах и будет автоматически перестраивать дерево должным образом. Подпрограммы преобразования не могут менять другие узлы в дереве, однако они могут обращаться к ним и ссылаться на них. Эти программы могут быть перезагружены, чтобы автоматически различать разные субклассы IR. Любому данному посетителю узла нужно лишь переопределить интересующие программы для нужных типов IR.

Имеется несколько субклассов Visitor, описывающих разные типы посетителей.

Субкласс

Описание

Inspector

Простой посетитель, собирающий информацию, но не меняющий узлы.

Modifier

Простой посетитель, которые не меняет структуру дерева, но может менять узлы.

Transform

Полное преобразование узла

PassManager

Комбинация нескольких типов, выполняемых последовательно.

Имеется также два интерфейса, которые могут реализовать субклассы Visitor для изменения режима посещения узлов.

Интерфейс

Описание

ControlFlowVisitor

Посещение узлов в порядке потока управления, расщепление (клонирование) посетителя по условию и слияние клонов в точке соединения.

Backtrack

Посетитель уведомляется, когда происходит возврат, и может сохранить свое состояние как точку возврата.

ControlFlowVisitor

Для поддержки анализа потока управления имеется специальный интерфейс ControlFlowVisitor,
который
понимают определенные субклассы
IR, инкапсулирующие поток управления, и применяют для управления порядком посещения узлов-потомков и состоянием посетителя, передаваемым его подпрограммам preorder/postorder для анализа потока управления.

Базовая идея заключается в том, что узлы потока управления в IR при посещении своих потомков создают клоны объектов ControlFlowVisitor для посещения различных потомков после ветвления и вызывают функцию flow_merge в точке соединения для слияния состояний посетителя. Базовый класс Visitor и интерфейс ControlFlowVisitor определяют две виртуальные функции — flow_clone и flow_merge решающие эти задачи. В базовом классе Visitor (используется всеми посетителями, не наследующими из интерфейса ControlFlowVisitor), где функции реализованы в виде no-ops, функция flow_clone просто возвращает тот же объект, а flow_merge не делает ничего. В интерфейсе ControlFlowVisitor функция flow_clone вызывает функцию clone (которая должна быть реализована классом leaf), а flow_merge является абстрактной виртуальной функцией, которая должна быть реализована в классе leaf.

В качестве примера использования в программах посетителя IR рассмотрим класс IR::If, имеющих 3 дочерних элемента для посещения — утверждение (predicate) и два следствия (consequent), причем значение утверждения определяет, какое из следствий выполняется. Дочерний посетитель для IR::If имеет вид

    visitor.visit(predicate);
    clone = visitor.flow_clone();
    visitor.visit(ifTrue);
    clone.visit(ifFalse);
    visitor.flow_merge(clone);

Таким образом оба следствия посещаются с состоянием посетителя, соответствующим потоку управления, сразу после оценки утверждения, а затем состояния потока управления после обоих посещений объединяются.

Для использования этой функциональности субклассу Visitor нужно лишь наследовать от интерфейса ControlFlowVisitor и реализовать функции clone и flow_merge. Другим классам Visitor не нужно делать ничего, но они могут реализовать clone по иным причинам.

Этот анализ потока управления работает лишь с потоком управления «ветвление-слияние» и не подходит для циклов (которые обычно требуют итерации процесса до фиксированной точки). Поскольку IR (в настоящее время) не поддерживает циклы, это не создает проблем. Язык P4 не разрешает включать циклы в таблицы «сопоставление-действие» внутри конвейера и это работает достаточно хорошо, но может быть недостаточно для синтаксических анализаторов, которые могут поддерживать циклы.

Поток в целом

Поток компиляции можно грубо разделить на три части — frontend, mid-end и backend. Интерфейсная часть (frontend) выполняет в основном не связанные с целевой платформой преобразования, предназначенные для очистки IR и приведения в каноническую форму. Средняя часть (middleend) выполняет зависящие от целевой платформы преобразования IR. Последняя часть (backend) принимает решения о выделении ресурсов. Поскольку этот этап может завершаться отказом по причине имеющихся ограничений, может потребоваться возврат к mid-end для проверки иной конфигурации. Возврата к начальному этапу (frontend) не предполагается. Разделение на этапы Front/Middle/Back достаточно произвольно и некоторые проходы могут перемещаться между этапами, если в этом будет смысл.

Frontend

Интерфейсная часть (frontend) анализирует исходный код и преобразует его в представление IR, напрямую соответствующее исходному коду. Затем выполняется серия проходов, таких как проверка типов (изменение узлов для указания выведенных типов), раскрытие констант, устранение неиспользуемого кода и т. п. Дерево первоначального IR соответствует исходному коду, а затем выполняются различные (в основном независимые от целевой платформы) преобразования. Сложная структура и сложные области действия в коде здесь преобразуются в «плоское» представление.

Mid-end

На этапе mid-end выполняются преобразования, в той или иной степени зависящие от целевой платформы, но не связанные с выделением конкретных ресурсов. В какой-то части этих преобразований представление IR преобразуется в фрому, соответствующую возможностям целевой платформы.

Управление проходами

Для управления возвратами и порядком проходов и преобразования служит объект PassManager, который инкапсулирует последовательность проходов и выполняет их по порядку. Проходы, поддерживающие интерфейс Backtrack, являются точками возврата.

Базовый объект PassManager может управлять динамически создаваемыми последовательностями проходов. Он отслеживает проходы, реализующие Backtrack, и при обнаружении отказа на последующем этапе возвращается к проходу Backtrack.

    interface Backtrack {        bool backtrack(trigger);     }

Метод backtrack вызывается при возникновении необходимости возврата и возвращает значение true, если в повторном проходе можно проверить применимый вариант. В противном случае (вариантов нет) возвращается false для поиска иной точки возврата.

Использование исключений

Исключения применимы при ошибках, о которых можно сообщить пользователю, проблемах с ограничениями, требующих возврата, и внутренних проблемах компилятора. Для этого создано множество классов исключений.

  • Backtrack::trigger служит для управления возвратом и может включать субклассы для добавления информации.

  • CompilerBug используется при внутренних ошибках компилятора. Взамен непосредственного вызова служит макрос BUG().

Классы IR

Чтобы упростить управление и расширений для классов IR, они определяются файлах .def, которые обрабатываются программой ir-generator, создающей код IR C++. По сути, файлы .def являются просто определениями классов C++ с удаленным шаблоном, который вставляет программа ir-generator, расщепляя также определение каждого класса на файлы .h и .cpp по мере необходимости.

Основная часть «нормального» IR определяется в каталоге ir, при этом некоторые frontend и backend используют свои расширения имен для IR, определяемых в их подкаталогах внутри ir. Программа ir-generator считывает все файлы .def в дереве и создает по одному файлу .h и .cpp, которые содержат весь код определения класса IR.

Все ссылки на объекты класса IR из объектов других классов IR должны быть указателями const, чтобы поддерживать инварианты, открытые лишь для записи (write-only). Когда это слишком обременительно, можно встраивать классы IR непосредственно в другие объекты IR, вместо использования ссылок. При таком встраивании объектов IR они становятся доступными для изменения посетителями Modifier
и
Transform при посещении содержащего объект узла, а сами объекты непосредственно посещаться не будут.

Программа ir-generator понимает оба эти механизма. Любое поле, объявленное в классе IR с другим классом IR в качестве типа, будет преобразовано в указатель const, если у него нет встроенного модификатора (в данном случае это будет встроенный напрямую субобъект).

Программа ir-generator понимает множество «стандартных» методов для классов IR — visit_children, operator==, dbprint, toString, apply. Эти методы можно объявлять без аргументов и типов возврата (т. е. просто указывать имя метода с блоком кода в фигурных скобках — {}), при этом будет синтезировано стандартное объявление. Если метод не объявлен в файле .def, создается стандартное определение (наоснове объявленных в классе полей). Таким способом большинство классов могут избежать включения этого шаблонного кода в файл .def.

IR::Node

Это основной абстрактный класс для всех узлов IR, содержащий лишь небольшое количество данных для информирования об ошибках и отладки. В общем случае эти данные никогда не сравниваются на предмет равенства (субклассам никогда не следует вызывать Node::operator==`), поскольку узлы (Node), различающиеся лишь этой информацией следует считать эквивалентными и не требовать для них клонирования или дерева IR.

IR::Vector<T>

Этот шаблонный класс содержит вектор указателей (const) на узлы конкретного субкласса IR::Node.

Перевод на русский язык

Николай Малых

nmalykh@protocols.ru

1Intermediate representation.

2Directed acyclic graph — ориентированный ациклический граф (орграф), в котором отсутствуют направленные циклы, но могут быть «параллельные» пути, выходящие из одного узла и разными путями приходящие в конечный узел.




simple_switch_CLI

Консольный интерфейс simple_switch_CLI

PDF

Версия 1.0

24.09.2020

Оглавление

Исключено в варианте HTML

Программы runtime_CLI и simple_switch_CLI служат для управления программными коммутаторами и маршрутизаторами из пакета BMv2, распространяемого в исходных кодах. Эти программы, по сути, представляют собой прототип плоскости управления (control-plane), полнофункциональной реализацией которой в SDN служит контроллер (например, P4Runtime).

Документ подготовлен на основе представленного разработчиками описания.

Большая часть описанных здесь команд работает в runtime_CLI и simple_switch_CLI, несколько команд применимы лишь в simple_switch_CLI и помечены как [simple_switch_CLI].

Управление программными реализациями прототипов устройств BMv2 выполняется через встроенный в эти реализации сервер Thrift. Интерфейс управления в настоящее время имеет достаточно ограниченную функциональность и позволяет выполнять лишь небольшой набор операций управления коммутатором:

  • просмотр состояния коммутатора и его портов;
  • добавление и удаление портов, без возможности управления их состоянием;
  • просмотр, добавление и удаление записей синтаксического анализатора (parser);
  • просмотр и изменение имеющихся таблиц сопоставления, без возможности добавления и удаления таблиц;
  • просмотр и ограниченное управление действиями в таблицах сопоставления;
  • просмотр и ограниченное управление клонированием пакетов для multicast-групп;
  • чтение, запись и сброс значений счетчиков и регистров, определенных в программе P4;
  • сохранение конфигурации в файл, загрузка новой конфигурации из файла, смена конфигурации.

По умолчанию сервер управления коммутатором доступен через порт 9090. При необходимости номер порта можно изменить с помощью опции —thrift-port в командной строке simple_switch (плоскость данных) и simple_switch_CLI (плоскость управления).

Команды управления портами

Набор команд управления портами в simple_switch достаточно скромен и позволяет лишь добавлять или удалять порты, связанные с физическими или логическими сетевыми интерфейсами аппаратной платформы. Включение (up) или отключение (down) порта из командного интерфейса не поддерживается.

show_ports

Команда без параметров, выводящая номера портов, имена связанных с ними интерфейсов и их состояние (up или down). Для портов коммутатора, связанных с логическими (виртуальными) интерфейсами аппаратной платформы команда всегда выводит состояние DOWN, хотя эти порты реально работают и способны принимать и передавать пакеты.

port_add

Добавляет в коммутатор порт, связанный с физическим или логическим интерфейсом платформы. Результат добавления зависит от используемого менеджера устройств. Команда принимает в качестве параметров имя интерфейса и номер порта. Дополнительно может указываться также путь к программе pcap, служащей для записи или «воспроизведение» сохраненных ранее пакетов.

port_add <iface_name> <port_num> [pcap_path]

Отметим, что для портов коммутатора simple_switch, связанных с логическими (виртуальными) интерфейсами платформы команда show_ports всегда показывает состояние DOWN, хотя эти порты реально работают и способны принимать и передавать пакеты.

port_remove

Удаляет порт из коммутатора. Результат удаления зависит от используемого менеджера устройств. Команда принимает в качестве параметра номер удаляемого порта.

port_remove <port_num>

Команды управления профилями действий

Концепция профилей действий (операций) в таблицах сопоставления использовалась в спецификации P414, но была исключена из P416. Тем не менее она сохранена в архитектуре V1Model, используемой simple_switch с программами P416. Ниже приведен фрагмент спецификации P414, связанный с профилями деййствий в таблицах сопоставления.

В некоторых экземплярах значения параметров действий (action) не специфичны для совпадающей записи и могут совместно использоваться несколькими записями. Это можно выразить в P4 с помощью профилей действий, которые представляют собой объявляемую структуру, задающую список возможных действий, а также могущую включать иные атрибуты.

Записи размещаются в процессе работы для указания одного действия (из числа указанных в профиле действий), которое будет выполняться при выборе данной записи, а также используемые значения параметров действия.

Вместо статической привязки одной записи из профиля действий к каждой записи таблицы сопоставления, можно связать несколько записей из профиля действий с одной записью таблицы сопоставления и позволить системе (т. е. логике плоскости данных) динамически связывать одну из записей профиля действий с каждым классом пакетов. Такое поведение включается атрибутом dynamic_action_selection. При задании этого атрибута записи профиля действий могут собираться в группы в процессе работы, а запись таблицы сопоставления можно связать с группой записей профиля действий. Для задания конкретного механизма плоскости данных, выбирающего определенную запись профиля действий в группе, нужно предоставить селектор действий. Этот селектор выбирает конкретную запись профиля действий для каждого пакета (псевдо)случайно или предсказуемо на основе полей заголовка и/или метаданных.

Ниже представлено определение профиля действий в формате BNF.

action_profile_declaration ::=
	action_profile action_profile_name {
		action_specification
		[ size : const_expr ; ]
		[ dynamic_action_selection : selector_name ; ]
	}
action_specification ::=
	actions { [ action_name ; ] + }
action_selector_declaration ::=
	action_selector selector_name {
		selection_key : field_list_calculation_name ;
}

Профили действий задаются и применяются в соответствии с приведенным ниже соглашением.

  • Атрибут size указывает число записей, требуемых для профиля действий. Если заданный размер не поддерживается, при обработке объявления будет выдан сигнал об ошибке. При опущенном атрибуте размера нет гарантии создания нужного числа записей в профиле действий в процессе работы.

Далее описаны поддерживаемые в simple_switch_CLI команды для работы с профилями действий в таблицах сопоставления программ P4.

act_prof_add_member_to_group

Добавляет элемент в группу в профиле действия (операции). Параметрами команды служат имя профиля действия, идентификатор элемента и идентификатор группы.

act_prof_add_member_to_group <action profile name> <member handle> <group handle>

act_prof_create_group

Добавляет группу в профиль действия (операции). Параметром команды служит имя профиля действия.

act_prof_create_group <action profile name>

act_prof_create_member

Добавляет элемент в профиль действия (операции). Параметрами команды служат имя профиля действия и имя действия, за которым могут следовать параметры действия.

act_prof_create_member <action profile name> <action_name> [action parameters]

act_prof_delete_group

Удаляет группу из профиля действия (операции). Параметрами команды служат имя профиля действия и идентификатор удаляемой группы.

act_prof_delete_group <action profile name> <group handle>

act_prof_delete_member

Удаляет элемент из профиля действия (операции). Параметрами команды служат имя профиля действия и идентификатор удаляемого элемента.

act_prof_delete_member <action profile name> <member handle>

act_prof_dump

Выводит список записей в профиле действия (операции). Параметром команды служит имя профиля действия.

act_prof_dump <action profile name>

act_prof_dump_group

Выводит информацию о группе из профиля действия (операции). Параметрами команды служат имя профиля действия и идентификатор группы.

act_prof_dump_group <action profile name> <group handle>

act_prof_dump_member

Выводит информацию об элементе из профиля действия (операции). Параметрами команды служат имя профиля действия и идентификатор элемента.

act_prof_dump_member <action profile name> <member handle>

act_prof_modify_member

Изменяет элемент в профиле действия (операции). Параметрами команды служат имя профиля действия, имя действия и идентификатор изменяемого элемента, за которым могут следовать параметры действия.

act_prof_modify_member <action profile name> <action_name> <member_handle> [action parameters]

act_prof_remove_member_from_group

Удаляет элемент из группы в профиле действия (операции). Параметрами команды служат имя профиля действия, идентификатор удаляемого элемента и идентификатор группы.

act_prof_remove_member_from_group <action profile name> <member handle> <group handle>

show_actions

Команда без параметров. Для каждого действия в загруженной программе P4 выводит имя и список параметров с указанием для каждого параметра имени и размера в битах.

Команды для работы с таблицами

table_add

Добавляет действие в указанную именем таблицу.

RuntimeCmd: help table_add Add entry to a match table: table_add <table name> <action name> <match fields> => <action parameters> [priority]

Можно увидеть список имен всех таблиц и действий с помощью команд show_tables и show_actions. Если не используется аннотация @name в программе P4, принятое по умолчанию имя зачастую является иерархическим и начинается с имени элемента управления, в котором определена таблица или действие.

Поля сопоставления должны указываться в том же порядке, в котором заданы поля ключей поиска в программе P4, но без имен. Вывод команды show_tables показывает имена, типы сопоставления (ternary, range, lpm, exact) и размер в битах для каждой таблицы после строки mk=. Поля с типом сопоставления optional представляются как ternary в файле BMv2 JSON, а в simple_switch_CLI всегда идентичны ternary.

Числовые значения можно задавать в десятичном или шестнадцатеричном формате (префикс 0x).

Для удобства представления некоторых числовых значений (адресов) применяются специальные форматы:

  • 32-битовые значения можно указывать в формате адресов IPv4 (например, 10.1.2.3);
  • 128-битовые значения можно задавать подобно адресам IPv6 в нотации IETF (например, FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 или 1080::8:800:200C:417A) как описано в RFC 2732;
  • 48-битовые значения можно задавать подобно адресам Ethernet MAC с парами шестнадцатеричных цифр, разделенными двоеточиями (например, 00:12:34:56:78:9a).

Поля ключей поиска в таблице с типом сопоставления lpm должны иметь значение, за которым указан символ / и размер префикса (например, 0x0a010203/24). Размер префикса должен указываться десятичным числом. Может указываться префикс с размером 0 и такие записи будут соответствовать любому значению искомого поля.

Поля ключей поиска в таблице с типом сопоставления ternary задаются численным значением, за которым следует &&& и численная маска без пробелов между числами и &&&. Биты маски со значением 1 задают совпадение битов, а биты со значением маски 0 не рассматриваются при сопоставлении. Т. е. запись value&&&mask будет соответствовать ключу поиска k, если (k & mask) == (value & mask), где операция & выполняется для каждого бита, как в P4. Для соответствия любому значению при троичном поиске можно задать 0&&&0.

Поля ключей поиска в таблице с типом сопоставления optional в исходном коде P4 задаются так же, как поля с типом сопоставления ternary. Отметим, что CLI при работе не проверяет наличие в маске только нулей или только 1, что позволяет задавать произвольные ternary-маски, даже если это не следует разрешать. Было бы хорошо ограничить задание сопоставления типа optional, но это потребует внесений существенных изменений в код behavioral-model.

Поля ключей поиска в таблице с типом сопоставления range задаются минимальным значением, за которым без пробела следует -> и максимальное значение (без пробела). Для соответствия любому значению из диапазона можно указать 0->255 для 8-битового поля, 0->0xffffffff для 32-битового и т. п.

Если любое поле ключа поиска в таблице имеет тип сопоставления ternary, optional или range, в записи должно быть указано числовое значение приоритета. Для полей с другими типами сопоставления задание приоритета является ошибкой. Если ключу поиска соответствует несколько записей таблицы, из них выбирается запись с меньшим числовым значением приоритета и выполняется действие из этой записи.

Параметры действий должны указываться в том же порядке, как они заданы в программе P4, но без имен. Вывод команды show_actions показывает имена и размеры в битах для всех параметров каждого действия.

table_clear

Удаляет все записи в таблице сопоставления (прямого или опосредованного), указанной именем, оставляя лишь запись default.

table_clear <table name>

table_delete

Удаляет из указанной именем таблицы заданную идентификатором запись.

table_delete <table name> <entry handle>

table_dump

Выводит дамп указанной именем таблицы.

table_dump MyIngress.ipv4_lpm 
========== 
TABLE ENTRIES 
========== 
Dumping default entry 
Action entry: MyIngress.drop -  
==========

table_dump_entry, table_dump_entry_from_key

Выводит дамп записи из указанной по имени таблицы. Запись задается идентификатором или ключом поиска.

table_dump_entry <table name> <entry handle>
table_dump_entry_from_key <table name> <match fields> [priority]

table_dump_group, table_dump_member

Выводит информацию (дамп) о группе или элементе из указанной именем таблицы. Группа или элемент задаются идентификатором.

table_dump_group <table name> <group handle>
table_dump_member <table name> <member handle>

table_indirect_add

Добавляет запись в таблицу непрямого сопоставления, указанную именем. Параметрами команды являются поля сопоставления и идентификатор элемента таблицы, может также указываться значение приоритета.

table_indirect_add <table name> <match fields> => <member handle> [priority]

table_indirect_add_member_to_group

Заменена командой act_prof_add_member_to_group.

table_indirect_add_with_group

Добавляет запись в таблицу непрямого сопоставления, указанную именем. Параметрами команды являются поля сопоставления и идентификатор группы, может также указываться значение приоритета.

table_indirect_add <table name> <match fields> => <group handle> [priority]

table_indirect_create_group

Заменена командой act_prof_create_group.

table_indirect_create_member

Заменена командой act_prof_create_member.

table_indirect_delete

Удаляет запись из заданной именем таблицы непрямого сопоставления. Запись указывается идентификатором.

table_indirect_delete <table name> <entry handle>

table_indirect_delete_group

Заменена командой act_prof_delete_group.

table_indirect_delete_member

Заменена командой act_prof_delete_member.

table_indirect_modify_member

Заменена командой act_prof_modify_member.

table_indirect_remove_member_from_group

Заменена командой act_prof_remove_member_from_group.

table_indirect_reset_default, table_indirect_set_default

Сбрасывает или устанавливает принятое по умолчанию действие в таблице непрямого сопоставления указанной именем. Устанавливаемое действие задается идентификатором элемента.

table_indirect_reset_default <table name>
table_indirect_set_default <table name> <member handle>

table_indirect_set_default_with_group

Устанавливает используемую по умолчанию группу в заданной именем таблице непрямого сопоставления. Группа указывается идентификатором.

table_indirect_set_default <table name> <group handle>

table_info

Выводит информацию об указанной именем таблице сопоставления.

table_info <table name>

table_modify

Добавляет запись в указанную именем таблицу сопоставления. Запись задается именем добавляемого действия и идентификатором, а также может включать параметры действия.

table_modify <table name> <action name> <entry handle> [action parameters]

table_num_entries

Возвращает число записей в таблице прямого или опосредованного сопоставления, указанной именем.

table_num_entries <table name>

table_reset_default

RuntimeCmd: help table_reset_default Reset default entry for a match table: table_reset_default <table name>

Меняет действие, выполняемое в таблице при отсутствии соответствующей ключу поиска записи на исходное значение, заданное в программе P4. Если в программе такое действие не задано, используется «пустая операция» — no-op (иногда ее называют NoAction).

table_set_default

RuntimeCmd: help table_set_default Set default action for a match table: table_set_default <table name> <action name> <action parameters>

Назначает действие, выполняемое таблицей при отсутствии записи, соответствующей ключу поиска.

Задание параметров действия рассмотрено в описании команды table_add.

table_set_timeout

Устанавливает время ожидания (тайм-аут) для указанной идентификатором записи в заданной именем таблице. Время ожидания указывается в миллисекундах.

table_set_timeout <table_name> <entry handle> <timeout>

show_tables

Команда без параметров. Для каждой таблицы в загруженной программе P4 выводит имя, реализацию (часто None, но может выводиться профиль или селектор действия для таблиц, созданных с этими опциями) и список полей поиска в таблице (ключей) с указанием для каждого поля имени, типа соответствия (например, exact, lpm, ternary, range) и размера в битах. Поля с типом соответствия optional в коде P4 представляются в файле BMv2 JSON как ternary.

table_show_actions

Выводит список действий заданной именем таблицы в порядке их представления в программе P4. Например,

RuntimeCmd: table_show_actions MyIngress.ipv4_lpm 
MyIngress.drop                 [] 
MyIngress.ipv4_forward         [dstAddr(48),    port(9)] 
NoAction

Команды управления синтаксическим анализатором

pvs_add, pvs_clear, pvs_get, pvs_remove

Эти команды служат для управления экземпляром набора значений синтаксического анализатора P4. Команда pvs_add добавляет запись в набор значений, pvs_remove удаляет запись, pvs_clear удаляет все записи, а pvs_get служит для просмотра имеющихся записей.

Язык P4 позволяет передавать value_set в качестве параметра структурные типы, как показано ниже.

struct vsk_t { bit<16> f1; bit<7> f2; } value_set<vsk_t>(4) pvs; select (<X>) { pvs: accept; _: reject; }

При добавлении или удалении записи требуется указать целое число, соответствующее сжатой разрядности набора значений, иначе CLI будет сообщать об ошибке. Когда записи набора значений состоят из множества отдельных полей, как в приведенном выше примере, сжатая разрядность определяется как сумма разрядностей этих полей без учета заполнения. Когда параметр типа для набора значений является целым числом (со знаком или без него) или структурой с одним полем, сжатая разрядность просто совпадает с размером этого поля.

В будущем CLI сможет поддерживать более понятный интуитивно интерфейс и позволит предоставлять целочисленные значения для разных полей, составляющих запись набора значений. Однако в настоящее время размер приходится вычислять самостоятельно.

Отметим, что pvs_add не выдает предупреждений при попытке добавить уже имеющуюся запись, а pvs_remove не предупреждает о попытке удалить отсутствующее значение.

Реализация наборов значений в bmv2 при сопоставлении поддерживает лишь точное совпадение (exact).

show_pvs

Команда без параметров. Для каждого набора значений анализатора в загруженной программе P4 выводит имя и ожидаемый размер записей. При добавлении записей командой pvs_add требуется указывать целочисленное значение, не превышающее этот размер.

Команды для работы с групповыми пакетами

mc_dump, mc_mgrp_create, mc_mgrp_destroy, mc_node_associate, mc_node_create, mc_node_destroy, mc_node_dissociate

При завершении входной обработки в архитектуре v1model входной код P4 может устанавливать значения нескольких полей в standard_metadata, контролирующих отбрасывание пакетов, передаваемых индивидуально (unicast) в один выходной порт, или групповых пакетов, реплицированных в список (возможно пустой) выходных портов. Более подробное описание приведено в разделе «Псевдокод для завершения входной и выходной обработки» описания bmv2.

Буфер пакетов содержит машину репликации PRE1.

В v1model.p4 standard_metadata поле mcast_grp задает один из 65535 идентификаторов multicast-групп из диапазона [1, 65535]. Значение mcast_grp = 0 задает отсутствие групповой репликации пакетов. Коммутатор simple_switch может использовать большее число идентификаторов групп и указанный диапазон задан определением поля mcast_grp в standard_metadata (файл v1model.p4) с типом bit<16>.

Для реплицируемых пакетов PRE находит в таблице значение mcast_grp и по нему получает (возможно пустой) список пар (egress_port, egress_rid), для которых создаются копии пакета. Для каждой копии позднее выполняется выходная обработка с двумя полями standard_metadata, инициализированными значениями найденной в таблице пары.

Отметим, что таблица групповой репликации не является таблицей, определяемой в программе P4. Здесь термин «таблица» имеет более общий смысл. Таблица репликации похожа на обычную таблицу P4 в том смысле, что поле mcast_grp является единственным ключом поиска и для него всегда применяется точное совпадение. Однако имеется ряд перечисленных ниже различий.

  • Таблица не объявляется в программе и присутствует в simple_switch независимо от загруженной программы P4 и применения в программе этой таблицы.
  • Программы плоскости управления используют для чтения и записи в таблицу групп иные вызовы API, нежели для таблиц P4.
  • Результатом поиска в таблице является список с переменным числом пар, тогда как язык P4 не имеет простого способа представления списков переменного размера в процессе работы.

В управление списками групповой рассылки на деле включены два «уровня» объектов:

  • каждая multicast-группа является множеством узлов;
  • каждый создаваемый узел групповой рассылки имеет одно значение egress_rid и набор egress_port, в котором не может быть дубликатов.

Предположим, что нужно настроить simple_switch так, чтобы пакеты для группы с mcast_grp = 1113 рассылались в приведенный ниже список пар (egress_port, egress_rid):

  • (egress_port=0, egress_rid=5);
  • (egress_port=7, egress_rid=10);
  • (egress_port=3, egress_rid=5).

Первая и последняя запись имеют общее значение egress_rid = 5, поэтому они могут находится в одном multicast-узле, который мы хотим создать в этом примере. Делается это командой mc_node_create, как показано ниже.

RuntimeCmd: help mc_node_create Create multicast node: mc_node_create <rid> <space-separated port list> [ | <space-separated lag list> ]

Команда имеет два обязательных параметра, задающих идентификатор и список выходных портов, а также позволяет указать список агрегированных портов (LAG) для групповой рассылки. Создадим узел для рассылки в порты 0 и 3.

RuntimeCmd: mc_node_create 5 0 3 Creating node with rid 5 , port map 1001 and lag map node was created with handle 0

Отметим дескриптор 0 в выводе команды. При каждом создании multicast-узла программа simple_switch выбирает целочисленный идентификатор для его именования, который должен использоваться при указании данного узла в последующих командах. Значения обычно начинаются с 0 и увеличиваются по мере создания новых узлов, но лучше не полагаться на это и смотреть вывод команды.

Создадим другой узел для копирования пакетов с egress_rid = 10, в нашем примере включающий лишь порт 7

RuntimeCmd: mc_node_create 10 7 Creating node with rid 10 , port map 10000000 and lag map node was created with handle 1

Вывод показывает, что этому узлу был назначен дескриптор 1.

Далее создадим группу с идентификатором 1113 командой mc_mgrp_create.

RuntimeCmd: help mc_mgrp_create Create multicast group: mc_mgrp_create <group id>

Справка говорит, что команда имеет единственный параметр — идентификатор группы.

RuntimeCmd: mc_mgrp_create 1113 Creating multicast group 1113

Здесь не используются специальные дескрипторы. Группы всегда именуются по заданным для них идентификаторам (1113 в этом примере).

После этого можно посмотреть созданные группы с помощью команды mc_dump.

RuntimeCmd: mc_dump ========== MC ENTRIES ********** mgrp(1113) ========== LAGS ==========

Вывод показывает наличие группы 1113, с которой не связано никаких узлов. В последующих примерах будут показаны группы с multicast-узлами. В текущей конфигурации simple_switch, передача пакета для репликации с mcast_grp = 1113 будет создавать 0 копий пакета, что равносильно его отбрасыванию (drop).

Для добавления multicast-узла в группу служит команда mc_node_associate.

RuntimeCmd: help mc_node_associate Associate node to multicast group: mc_node_associate <group handle> <node handle>

Команда принимает два параметра, один из которых задает идентификатор группы, другой — узла.

RuntimeCmd: mc_node_associate 1113 0 Associating node 0 to multicast group 1113

После добавления узла группа выглядит уже иначе.

RuntimeCmd: mc_dump ========== MC ENTRIES ********** mgrp(1113)   -> (L1h=0, rid=5) -> (ports=[0, 3], lags=[]) ========== LAGS ==========

Видно, что группа 1113 имеет один узел. L1h является сокращением для записей L1 handle, которые можно увидеть в системном журнале simple_switch (это просто идентификатор узла). Показано значение rid = 5 (rid — сокращение для egress_rid), и список портов 0 и 3.

В текущей конфигурации simple_switch передача пакета для репликации с mcast_grp = 1113 будет создавать 2 копии пакета с парами:

  • (egress_port=0, egress_rid=5);
  • (egress_port=3, egress_rid=5).

Добавим в группу 1113 второй узел, упомянутый выше.

RuntimeCmd: mc_node_associate 1113 1 Associating node 1 to multicast group 1113

Группа после этого будет иметь вид

RuntimeCmd: mc_dump ========== MC ENTRIES ********** mgrp(1113)   -> (L1h=0, rid=5) -> (ports=[0, 3], lags=[])   -> (L1h=1, rid=10) -> (ports=[7], lags=[]) ========== LAGS ==========

В текущей конфигурации simple_switch передача пакета для репликации с mcast_grp = 1113 приведет к созданию трех копий с парами:

  • (egress_port=0, egress_rid=5);
  • (egress_port=3, egress_rid=5);
  • (egress_port=7, egress_rid=10).

Можно исключить узел из группы с помощью команды mc_node_dissociate.

RuntimeCmd: help mc_node_dissociate Dissociate node from multicast group: mc_node_associate <group handle> <node handle>

В качестве параметров команды нужно указать группу и дескриптор исключаемого узла.

RuntimeCmd: mc_node_dissociate 1113 0 Dissociating node 0 from multicast group 1113

Группа после исключения узла будет иметь вид

RuntimeCmd: mc_dump ========== MC ENTRIES ********** mgrp(1113)   -> (L1h=1, rid=10) -> (ports=[7], lags=[]) ========== LAGS ==========

В этой конфигурации simple_switch передача пакета для репликации с mcast_grp = 1113 будет создавать 1 копию с (egress_port=7, egress_rid=10).

При исключении узла 1 из группы 1113, она вернется в исходное состояние и групповые пакеты будут отбрасываться.

RuntimeCmd: mc_node_dissociate 1113 1 Dissociating node 1 from multicast group 1113 RuntimeCmd: mc_dump ========== MC ENTRIES ********** mgrp(1113) ========== LAGS ==========

Можно удалить группу с помощью команды mc_mgrp_destroy.

RuntimeCmd: help mc_mgrp_destroy Destroy multicast group: mc_mgrp_destroy <group id>

Единственным параметром команды является идентификатор группы.

RuntimeCmd: mc_mgrp_destroy 1113 Destroying multicast group 1113

После этого команда просмотра групп будет давать пустой вывод.

RuntimeCmd: mc_dump ========== MC ENTRIES ========== LAGS ==========

Для описанных операций есть ряд ограничений.

  • Для привязки узла к группе нужно сначала создать то и другое. Порядок создания не имеет значения.
  • Коммутатор simple_switch позволяет связать узел лишь с одной группой. Для привязки узла к другой группе его нужно сначала отсоединить от текущей группы.
  • При удалении группы рекомендуется сначала удалить привязки узлов к ней.
  • В одном узле значение egress_port не может повторяться. Если нужно отправить в порт несколько копий, следует создать дополнительные узлы. Использование разных egress_rid для этих узлов позволяет коду P4 различать копии и по разному обрабатывать их.

Команда mc_dump выводит информацию об узлах, привязанных к группам. Для просмотра узлов, не связанных с группой, в simple_switch_CLI нет команды.

Отметим, что в справке simple_switch_CLI и выводе команды mc_dump указываются группы lag и их списки, которые еще не документированы. Однако групповую репликацию можно использовать и без них.

mc_node_update

Обновляет групповой узел, принимая в качестве параметра идентификатор узла и список разделенных пробелами номеров портов. Дополнительно может указываться список разделенных пробелами LAG.

mc_node_update <node handle> <space-separated port list> [ | <space-separated lag list> ]

mc_set_lag_membership

Задает набор портов, включаемых в группу LAG. Параметрами служат индекс LAG и список разделенных пробелами номеров портов коммутатора.

mc_set_lag_membership <lag index> <space-separated port list>

Программы управления клонированием пакетов

mirroring_add, mirroring_add_mc, mirroring_delete, mirroring_get

[simple_switch_CLI]

При обработке пакетов могут происходить перечисленные ниже события:

  • программа с архитектурой P416 v1model вызывает внешнюю функцию clone или clone3 при входной или выходной обработке и на этом соответствующая обработка завершается;
  • программа P414 вызывает clone_ingress_pkt_to_egress при входной обработке, которая на этом завершается;
  • программа P414 вызывает clone_egress_pkt_to_egress при выходной обработке, которая на этом завершается.

В этих случаях не только «исходный» пакет продолжит прохождение через конвейер, но и пакеты, которые могут быть созданы PRE в составе simple_switch (копии или клоны) из текущего пакета, будут помещены в очередь в буфере пакетов и позднее для каждого из них будет выполнена выходная обработка. Каждый из клонов обрабатывается независимо от других и от исходного пакета. Дополнительную информацию можно найти поиском по слову «клон» в разделе «Псевдокод для завершения входной и выходной обработки» описания bmv2.

В simple_switch поддерживается до 32768 независимых сессий клонирования с номерами от 0 до 32767, которые называют также сессиями отражения (mirroring session). При клонировании пакета указывается желаемая сессия для вызова из программы P4 операции клонирования.

Целью команд отражения является указание портов, в которые передаются клонированные пакеты. Это настраивается независимо для каждой сессии клонирования по ее идентификатору.

По умолчанию при старте simple_switch сессии клонирования не настроены. Любая операция клонирования для ненастроенной сессии не будет создавать клонов, т. е поведение обработки пакетов пойдет обычным путем, как будто клонирование не запрашивалось.

RuntimeCmd: help mirroring_add Add mirroring session to unicast port: mirroring_add <mirror_id> <egress_port>

Например, по команде mirroring_add 5 7 в конфигурации сессии с идентификатором 5 будет задано создание операцией клонирования в сессии 5 одного клона, предназначенного для выходного порта 7.

RuntimeCmd: mirroring_add 5 7 RuntimeCmd: mirroring_get 5 MirroringSessionConfig(mgid=None, port=7)

Можно задать в конфигурации сессии клонирование пакетов в multicast-группу.

RuntimeCmd: help mirroring_add_mc Add mirroring session to multicast group: mirroring_add_mc <mirror_id> <mgrp>

Команда mirroring_add_mc 5 22 изменит конфигурацию сессии 5 так, что последующие операции клонирования для этой сессии будут выполнять множественное клонирование для multicast-группы 22. Команда mc_mgrp_create и другие команды работы с группами описаны выше.

RuntimeCmd: mirroring_add_mc 5 22 RuntimeCmd: mirroring_get 5 MirroringSessionConfig(mgid=22, port=None)

Можно сбросить состояние настроенной сессии клонирования по ее идентификатору.

RuntimeCmd: help mirroring_delete Delete mirroring session: mirroring_delete <mirror_id>

Команда mirroring_delete 5 возвращает сессию 5 в исходное (ненастроенное) состояние.

RuntimeCmd: mirroring_delete 5

С помощью команды mirroring_get можно увидеть состояние указанной идентификатором сессии клонирования. Например, для сброшенной предыдущей командой сессии 5 вывод будет иметь вид

RuntimeCmd: mirroring_get 5 Invalid mirroring operation (SESSION_NOT_FOUND)

Команды для работы с измерителями

meter_array_set_rates

Задает скорости для всего массива измерителей, принимая в качестве параметров имя, а также набор значений скоростей и пиков.

meter_array_set_rates <name> <rate_1>:<burst_1> <rate_2>:<burst_2> ...

meter_get_rates

Возвращает параметры измерителя по имени и индексу.

meter_get_rates <name> <index>

meter_set_rates

Синтаксис команды в CLI приведен ниже.

RuntimeCmd: help meter_set_rates Configure rates for a meter: meter_set_rates <name> <index> <rate_1>:<burst_1> <rate_2>:<burst_2> ...

Поведение измерителя соответствует RFC 2697 и RFC 2698.

Пользователь задает скорость для измерителя в байтах за микросекунду и burst_size в байтах. Для измерителей, учитывающих пакеты, скорость задается числом пакетов за микросекунду, burst_size — числом пакетов.

Команды для работы со счетчиками

counter_read, counter_reset, counter_write

Считывает, сбрасывает или задает значение указанного именем счетчика. При чтении и записи команда включает индекс, а при записи дополнительно указываются записываемые в счетчик значения числа пакетов и байтов.

Команды для работы с регистрами

register_read, register_write

Читает или записывает регистр, указанный именем и индексом. При записи также указывается вносимое в регистр значение.

register_reset

Сбрасывает в 0 все ячейки массива регистров, указанного именем.

register_reset <name>

Команды управления очередями

set_queue_depth

[simple_switch_CLI]

Задает размер всех или указанной выходной очереди, принимая в качестве параметров число пакетов в очереди и необязательный номер порта. По умолчанию устанавливается размер очередей на всех выходных портах.

set_queue_depth <nb_pkts> [<egress_port>]

set_queue_rate

[simple_switch_CLI]

Задает скорость (пакет/с) всех или указанной выходной очереди, принимая в качестве параметров скорость для очереди и необязательный номер порта. По умолчанию устанавливается скорость для очередей на всех выходных портах.

set_queue_rate <rate_pps> [<egress_port>]

Прочие команды

help

При запуске без параметра выводит список поддерживаемых интерфейсом CLI команд. При указании в качестве параметра имени той или иной команды CLI будет выведена краткая справка по работе с командой. Например,

RuntimeCmd: help help 
List available commands with "help" or detailed help with "help cmd". RuntimeCmd: help show_tables 
List tables defined in the P4 program: show_tables

switch_info

Выводит базовую информацию о коммутаторе.

switch_info 
device_id                : 0 
thrift_port              : 9090 
notifications_socket     : ipc:///tmp/bmv2-0-notifications.ipc 
elogger_socket           : None 
debugger_socket          : None

serialize_state

Преобразует все состояния коммутатора в последовательную форму и выводит информацию в файл, заданный параметром команды.

reset_state

Сбрасывает все состояния коммутатора (таблицы, регистры и т. п.), сохраняя конфигурацию P4. Команда не имеет параметров.

get_time_elapsed, get_time_since_epoch

[simple_switch_CLI]

Возвращает время, прошедшее с момента запуска коммутатора или с начала «эпохи» в микросекундах. Команда не имеет параметров.

set_crc16_parameters, set_crc32_parameters

Задает параметры вычисления контрольных сумм crc16 или crc32.

set_crc16_parameters <name> <polynomial> <initial remainder> <final xor value> <reflect data?> <reflect remainder?>
set_crc32_parameters <name> <polynomial> <initial remainder> <final xor value> <reflect data?> <reflect remainder?>

load_new_config_file

Загружает конфигурацию коммутатора из заданного параметром файла JSON. Для активизации этой конфигурации служит команда Ошибка: источник перекрёстной ссылки не найден.

swap_configs

Меняет конфигурацию, используя файл, загруженный ранее командой load_new_config_file.

write_config_to_file

Считывает конфигурацию из используемого в данный момент файла JSON и записывает ее в указанный пользователем файл на станции управления, где запущена программа simple_switch_CLI или runtime_CLI. Если файл задан без абсолютного пути, он будет сохранен в каталоге, из которого была запущена программа.

shell

Позволяет выполнить из консоли коммутатора команду операционной системы, в которой коммутатор работает.

shell <command>

Николай Малых

nmalykh@protocols.ru

1Packet Replication «Engine» — «машина» репликации пакетов.