Базовые типы данных
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); }
Николай Малых