NPL — Network Programming Language Specification
v1.3
June 11, 2019
Перевод спецификации языка NPL, версия 1.3
1. Сфера применения
Этот документ описывает конструкции и применение языка сетевого программирования NPL1. Основной целью NPL является описание поведения обработки пакетов на уровне данных (Data Plane Packet Processing) с использованием подходящего набора конструкций. Приложение обработки пакетов в NPL включает конструкции высокого уровня для таких задач, как синтаксический анализ, таблицы «сопоставление-действие», редактирование пакетов. Язык также включает другие конструкции, такие как функции.
Поскольку основным требованием к языку является его отображение на гибко настраиваемое сетевое оборудование, полный набор конструкций сосредоточен на возможностях оборудования. Эти конструкции должны применяться при разработке программ.
Документ предназначен для системных архитекторов, инженеров-проектировщиков и инженеров-программистов, которым нужно понимать логику NPL, вносить изменения в программы NPL или разрабатывать приложения для обработки пакетов. Инженерам-тестировщикам также следует понимать NPL для выполнения эффективных тестов.
Документ не рассматривает архитектуру программируемых устройств и работу компиляторов NPL.
2. Термины
Ниже приведены определения терминов, концепций, символов и сокращений, используемых в документе.
NPL Compiler — компилятор NPL
Состоит компиляторов Front End (FE) и Back End (BE).
Front End (FE) Compiler — компилятор FE
Компонент компилятора, выполняющий синтаксический анализ и проверку корректности исходного кода NPL, а также генерирующий промежуточное представление.
Back End (BE) Compiler — компилятор BE
Компонент компилятора, создающий аппаратный код на основе IR.
IR files — файлы IR
Файлы промежуточного представления.
Constructs — конструкции
Встроенные компоненты для выполнения определенных функций.
Metadata — метаданные
Поля шины (bus), заголовка (header) и таблицы (table), которые не создаются в NPL, но присутствуют и доступны.
3. Обзор
Рост программно-определяемых сетей (SDN2) повысил уровень ожиданий в части программируемости и автоматизации сетей. Исходно задачей SDN было решение проблем уровня управления в стремлении преодолеть ограничения традиционных моделей управления. Впоследствии пользователям потребовались более гибкие решения, способные адаптироваться к изменению сетевых потребностей, например, поддержка новых наложенных протоколов и расширение возможностей телеметрии. Это расширило сферу применения SDN путем включения задач программирования уровня данных. Однако новые гибкие решения для коммутации должны обеспечивать производительность на полной скорости линии с оптимизацией ресурсов коммутатора и потребляемой мощности.
Хотя для программирования уровня данных можно разработать разные языке, важно обеспечить в них поддержку конструкций, способных программировать современные устройства. Для этого было разработан новый язык программирования — NPL, являющийся открытым языком высокого уровня для эффективного программирования уровня пересылки пакетов. NPL включает конструкции для описания поведения сети, использующие преимущества базового программируемого оборудования.
В своем первом вопрощении NPL обеспечивает все требуемые функции для реализации надежных коммутационных решений. NPL включает широкий набор компонент от типов данных для задания отдельных сигналов управления до конструкций высокого уровня, позволяющих взаимодействовать со сложными аппаратными блоками.
В типичном коммутаторе с фиксированными функциями используется набор таблиц и объектов для обработки пакетов, который сложно изменить после производства коммутатора. В программируемом коммутаторе элементы обработки пакетов может определять пользователь. NPL позволяет задать детали таблиц и других объектов для достижения нужного поведения коммутатора. NPL является специализированным языком программирования уровня данных, основанным на традиционных языках программирования, используемых уровнем управления для настройки путей коммутации данных.
В NPL применяется ряд описанных ниже абстракций.
Data Types — типы данных
Определяет тип поля данных.
Parser — синтаксический анализатор
Идентифицирует разрешенные заголовки в принятых пакетах и извлекает поля таких заголовков.
Logical Bus — логическая шина
Задает поля и наложения (overlay) логической шины, соединяющей объекты NPL.
Logical Table (Match Action table) — логическая таблица (сопоставление-действие)
Описывает конкретную таблицу с ключами поиска и действиями. NPL поддерживает таблицы index, hash, tcam, lpm, alpm.
Editor — редактор
Обеспечивает возможность добавлять, удалять или заменять заголовки.
Special Function — специальная функция
Механизм для вызова определенной аппаратной функции, которая может считаться интеллектуальной собственностью. Это обеспечивает структурированный механизм взаимодействия с такими функциями без раскрытия их содержимого.
Function — функция
Обеспечивает программируемую логику принятия решений без использования таблицы. Например, функция может служить для выбора среди нескольких результатов «сопоставление-действие» или ключа для сопоставления.
Strength Resolution – выбор среди таблиц
Механизм выбора одной из нескольких таблиц, одновременно (параллельно) обновляющих один объект.
Packet Drop, Packet Trace, Packet Count
Встроенные функции для отбрасывания, трассировки и учета пакетов.
Create Checksum, Update Packet Length
Встроенные функции для расчета контрольной суммы и обновления размера пакетов.
Metadata for MA and Parser — метаданные для таблиц и анализаторов
Данные, не создаваемые в NPL, но существующие в процессе работы с пакетом и доступные для использования в NPL.
Язык NPL не привязан к какой-либо аппаратной архитектуре и предназначен для реализации на разных аппаратных платформах, включая ASIC, программируемые NIC3, FPGA и программные коммутаторы. Хотя определенные конструкции языка предназначены для использования конкретных свойств оборудования, это не препятствует отображению программ на цели, не поддерживающие таких свойств.
Подобно другим языкам высокого уровня, для NPL нужен набор компиляторов и других инструментов, отображающих программы NPL на целевые аппаратные платформы. Компилятор FE (препроцессор) отвечает за проверку синтаксиса и семантики программы, а также создает промежуточное представление (IR). Компилятор BE отвечает за отображение промежуточных представлений на конкретную аппаратную платформу. Этот компилятор также генерирует интерфейс API, используемый уровнем управления для контроля поведения коммутатора. Компиляторы обеспечивают уровень распараллеливания, определяемый NPL и применяемым оборудованием.
3.1. Преимущества
Разработка NPL началась с обзора имеющихся сетевых платформ и способам предоставления пользователям возможностей программировать их с учетом программных и аппаратных аспектов. Результат требовал возможности раскрыть и применить преимущества базового оборудования. Решение также требовало от конечного пользователя эффективно задать поведение уровня данных и поднять это на уровень ОС и приложений с четким указанием намерений пользователя. Таким образом, был создан новый язык, позволяющий пользователям задать функциональность сетевого уровня данных с возможностью совместить свойства оборудования с задачами пользователя. Это NPL.
По сравнению с настраиваемыми и другими программируемыми решениями, доступными сегодня, NPL обеспечивает ряд преимуществ. Изощренные возможности языка обеспечивают:
-
настраиваемые конвейеры таблиц;
-
интеллектуальное выполнение действий;
-
параллельную обработку;
-
расширенные возможности логических таблиц;
-
уровень интегрированного инструментария;
-
простое, интуитивное управление потоками данных.
NPL также поддерживает конструкции, обеспечивающие включение библиотечных компонент, реализующих фиксированные функции апаратных блоков. Это позволяет описать множество приложений уровня данных с использованием NPL, от простой архитектуры на базе таблиц до сложных систем, включающих множество эффективных блоков. Конструкции языка позволяют выразить эти возможности, что обеспечивает значительный рост эффективности и снижает стоимость финальной аппаратной реализации. Конструкции NPL позволяют многократно использовать программный код при создании семейства устройств коммутации.
3.2. Архитектурная модель
NPL является языком высокого уровня, включающим все компоненты, требуемые для реализации надежного коммутационного решения. Эти компоненты начинаются с простых типов данных, позволяющих задать отдельные сигналы управления, и включают сложные конструкции высокого уровня для взаимодействия с аппаратными блоками.
На рисунке 1 показана архитектурная модель в виде блок-схемы базовых компонент NPL и связей между ними.
Рисунок 1. Архитектурная модель.
Каждый функциональный блок взаимодействует со своими соседями, читая или записывая данные в одну или несколько шин. Шина содержит набор полей, заданных с использованием NPL. Логически поток шин через блоки образует конвейер обработки. Например. Таблицы «сопоставление-действие» (СД), функции и специальные функции обычно читают поля шины и записывают в них. Блок анализа принимает пакет на входе и записывает значения полей в шину, а блок редактирования использует поля шины для обновления или создания выходного пакета.
Примером такой архитектурной модели может служить последовательность любого числа блоков, расположенных в произвольном порядке4.
В последующих разделах рассматриваются языковые конструкции, используемые при программировании этих базовых абстракций, с примерами.
4. Компоненты язык NPL
4.1. Поддерживаемые конструкции
Документ поделен в соответствии с функциональными частями конвейера в коммутаторе.
- Типы данных.
- Программные конструкции.
- Конструкции синтаксического анализатора.
- Конструкции шин.
- Конструкции таблиц «сопоставление-действие».
- Функции.
- Конструкции редактора.
- Конструкции специальных функций.
-
Метаданные для таблиц «сопоставление-действие» и анализатора.
Определения идентификаторов и констант NPL, а также полная грамматика NPL описаны в Приложении D.
-
Идентификаторы должны начинаться с символов [a-z A-Z _] и могут включать символы [a-z A-Z _ 0-9].
-
Десятичные и шестнадцатеричные литералы.
-
Строковые литералы (например, “foobar”)
В оставшейся части раздела приведены описаний каждой из поддерживаемых конструкций.
4.2. Типы данных
NPL поддерживает базовые типы данных bit, varbit, list, const и auto_enum, а также производный тип struct.
4.2.1. bit
Тип bit относится к базовым. Значение данного типа может быть 0 или 1. Тип служит для описания полей в производных типах данных, таких как struct. Этот тип также применяется в logical_table, logical_register, special_function и других конструкциях.
4.2.1.1. bit-array
Для описания многобитовых полей применяется тип bit-array с указанием размера. NPL не задает ограничений для размера битовых массивов. Массивы битовых массивов не поддерживаются в NPL.
Пример
bit cfi; // однобитовое поле
bit[3] pri; // 3-битовое поле pri
bit[12] vid; // 12-битовое поле vid
bit[128] bit_map; // 128-битовое поле bit_map
bit[8] label[5]; // не разрешего, поскольку массивы битовых массивов не поддерживаются
4.2.1.2. Индексирование bit-array
NPL поддерживает статические и переменные индексы для массивов. Изначально поддержка ограничена битовыми массивами. Размер массива и размер индекса должны соответствовать.
Статическое индексирование bit-array
Индекс задается целым числом без знака и может указывать один бит или диапазон битов.
Пример
local.rst1 = local.rpa_id_profile1[3:2]; local.rst1 = local.rpa_id_profile1[0:0];
Индексирование bit-array переменной
Индексирование массивов переменной обычно применяется для битовых полей (bitmap).
Пример
local.rst1 = local.rpa_id_profile1[ip_tmp_bus.idx:ip_tmp_bus.idx];
4.2.2. varbit
Тип varbit служит для задания битовых массивов переменного размера. Некоторые протоколы используют в заголовках поля, размер которых может меняться от пакета к пакету. Тип varbit[X] задает переменную, размер которой не может превышать X битов.
Пример
varbit[120] options; // опции размером да 120 битов.
4.2.3. const
Тип данных const используется для обозначения постоянных величин (integer или enum).
Пример
usage_mode_create(in const index, in bit[2] in_pkt_color, in varbit[14] meter_action_set, in varbit[10] color_table_index0, in varbit[8] color_pdd_sbr_index0, out bit[2] color );
4.2.4. list
В некоторых конструкциях NPL может требоваться объединение переменного числа элементов в список. Примерами могут служить dynamic_table, strength_resolve и create_checksum. Для представления этого служит тип данных list. Элементы списка должны указываться в фигурных скобках.
{ipv4.protocol, ipv4.dip}
Списки используются в следующих конструкциях:
-
dynamic_table для указания переменного числа входных и выходных полей;
-
update_checksum для указания переменного числа, учитываемых в контрльной сумме;
-
strength_resolve для указания переменного числа объектов, которые создают запись.
Пример
Аргументы dynamic_table используют list для указания полей, которые могут применяться в заранее выбранном шаблоне.
flex_digest_lkup.presel_template( { ing_cmd_bus.l2_iif_opaque_ctrl_id, ing_cmd_bus.vfi_opaque_ctrl_id, ing_cmd_bus.l2_iif_flex_digest_ctrl_id_a, ing_cmd_bus.l2_iif_flex_digest_ctrl_id_b, ing_cmd_bus.fixed_hve_iparser1_0, ing_cmd_bus.flex_hve_iparser1_1, ing_cmd_bus.fixed_hve_iparser2_0, ing_cmd_bus.flex_hve_iparser2_1, ing_cmd_bus.my_station_hit });
Конструкция create_checksum содержит аргумент list со списком полей, учитываемых в контрольной сумме.
create_checksum(egress_pkt.fwd_l3_l4_hdr.udp.checksum, { egress_pkt.fwd_l3_l4_hdr.ipv4.sa, egress_pkt.fwd_l3_l4_hdr.ipv4.da, editor_dummy_bus.zero_byte, egress_pkt.fwd_l3_l4_hdr.ipv4.protocol, egress_pkt.fwd_l3_l4_hdr.udp.udp_length, egress_pkt.fwd_l3_l4_hdr.udp.src_port, egress_pkt.fwd_l3_l4_hdr.udp.dst_port, egress_pkt.fwd_l3_l4_hdr.udp.udp_length, egress_pkt.fwd_l3_l4_hdr.udp._PAYLOAD });
4.2.5. struct
Тип struct служит для задания упорядоченного множества полей. Структуры применяются в разных типах конструкций. Внутри структур могут присутствовать лишь типы bit и struct. Тип struct поддерживает перекрытия (overlay) для указания полей несколькими способами
Таблица 1. Конструкция struct.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
struct |
Задает новую структуру с именем и полями. |
|
fields |
bit, bit[n], varbit или struct are allowed. Другие типы и конструкции не разрешены. | |
overlays |
Задает наложения для полей структуры. В структуре разрешается лишь одна конструкция overlays. Все наложения struct указываются в конструкции overlays. |
4.2.6. Массивы struct
В NPL разрешены одномерные массивы struct. NPL не ограничивает размер массива struct. Ниже приведены примеры использования массивов struct.
obj_bus.struct1[arr1].field = field; obj_bus.struct1[arr1].struct2[arr2].field = field; cmd_bus.struct1 = obj_bus.struct1[arr];
Пример
Простая структура
struct vlan_s { fields { bit cfi; // 1-битовое поле bit[3] pri; // 3-битовое поле pri bit[12] vid; // 12-битовое поле vid } }
Структура с наложением
struct switch_bus_s {
fields {
bit[4] otpid_enable;
bit olp_enable;
bit ts_enable;
bit[10] ing_port_num; // Базовое поле для определенных далее наложений.
bit svp_enable;
}
overlays {
ing_svp : ing_port_num[7:0];
ing_pri : ing_port_num[9:8]; // Наложения на базовое поле ing_port_num.
exp : ing_port_num[9:8];
}
}
Массив структур
struct mpls_header_stack_t { fields { mpls_t mpls[3]; // Здесь может быть 3 заголовка mpls_t. } }
Элементы массива можно указать в форме mpls[0], mpls[1], mpls[2].
4.2.7. enum
NPL поддерживает конструкцию enum для определения перечисляемых типов. Значения элементов enum должен предоставлять пользователь. Перечисляемое в NPL — это просто идентификатор, указывающий подмножество того, что предоставляется в C/C++, и не является типом данных в NPL. Перечисляемые служат для представления констант и определяют константы, используемые в качестве аргументов функций и rvalue в операторах присваивания.
Пример
enum drop_reason{ NO_DROP = 0, MEMBERSHIP_DROP = 1, TTL_DROP = 2 } packet_drop(drop_bus.disable_drop, drop_reason.TTL_DROP, 5);
4.2.8. auto_enum
NPL поддерживает тип данных auto_enum для задания перечисляемых типов. Значения элементов auto_enum назначает компилятор. Производитель целевой платформы может принять решение о способе отображения и присваивания значений auto_enum.
Типовыми применениями auto_enum являются Logical Table Lookup, Multi-Data View и Strength Based Resolution Index.
Целевой компилятор может задавать значения auto_enum на основе контекста их использования. Такие auto_enum должны быть глобальными и назначаться в одном экземпляре. Например, auto_enum из поиска в логических таблицах нельзя использовать в специальных функциях.
Пример
auto_enum qos_entry { QOS_DISABLE, QOS_L3_TUNNEL, QOS_L2_TUNNEL } qos_sfc.sf_profile_entry("sfc_qos_profile", qos_entry.QOS_L3_TUNNEL, { obj_bus.mapping_ptr, cmd_bus.effective_exp }, { cmd_bus.int_pri, cmd_bus.pri } );
4.3. Выражения
4.3.1. Обозначение чисел
NPL поддерживает только десятичные и шестнадцатеричные литеральные константы. Числовая нотация применяется при задании значений полей. NPL не поддерживает тип bool, значение 0 соответствует false, все прочие — true.
Пример
a = 5; // десятичное значение ipv4.ttl = 0xF; // шестнадцатеричное значение ipv6.dip = 0x01234567; ipv6.dip[63:0] = 0x0123456789abcdef;. if (ipv4.protocol == 0x23) ipv4.protocol = 0x231;
4.3.2. Условные операторы
NPL поддерживает операторы условий в разных конструкциях.
Таблица 2. Условные конструкции NPL.
Условные операторы |
Описание |
---|---|
if, else if, else |
Оператор if |
switch |
Оператор switch |
4.3.3. Операторы
NPL поддерживает множество операторов. Ограничения при их использовании рассмотрены ниже.
Таблица 3. Операторы NPL.
Оператор |
Символ |
Описание |
---|---|---|
Арифметические операторы |
+ |
Сложение |
— |
Вычитание |
|
* |
Умножение |
|
/ |
Деление |
|
% |
Деление по модулю |
|
Операторы отношений |
== |
Равно |
!= |
Не равно |
|
< |
Меньше |
|
<= |
Меньше или равно |
|
> |
Больше |
|
>= |
Больше или равно |
|
Оператор слияния |
<> |
Конкатенация |
Логические операторы |
&& |
Логическое И (AND) |
|| |
Логическое ИЛИ (OR) |
|
Операторы сдвига |
<< |
Сдвиг влево |
>> |
Сдвиг вправо |
|
Побитовые логические операторы |
& |
И |
| |
ИЛИ |
|
!A |
Отрицание (NOT) |
|
~A |
Дополнение ло 1 |
|
^ |
Исключающее ИЛИ (XOR) |
|
Унарные операторы |
&A |
Сокращение И (все биты 1) |
|A |
Сокращение ИЛИ (все биты 0) |
|
Оператор присваивания |
= |
Присваивание значений5 |
Оператор маскирования |
mask |
Применяется в операторе switch, трактуется как AND. Операнды слева и справа от = могут иметь разные размеры. Если левый операнд (lvalue) больше правого (rvalue) по размеру значение дополняется нулями. В противном случае компилятор возвращает ошибку. |
4.3.4. Области действия переменных
4.3.4.1. Глобальные переменные
Ниже приведен список переменных NPL с глобальной областью действия. Такие переменные должны иметь уникальные имена:
-
enum;
-
auto_enum;
-
struct;
-
bus;
-
packet;
-
logical_table;
-
logical_register;
-
parser_node;
-
function;
-
special_function;
-
dynamic_table;
-
strength.
4.3.4.2. Локальные переменные
Во многих конструкция используются переменные с локальной областью действия.
-
logical_table — поля, ключи;
- struct — поля, структуры;
- logical_register — поля;
- enum — элементы;
- auto_enum — элементы;
- special_function — методы;
- dynamic_table — методы.
4.4. Конструкция program
Программы представляют собой приложения NPL. Имя программы является точкой входа, как main в языке C. Конструкция program определяет порядок выполнения других конструкций конвейера обработки пакетов. Для управления потоком обработки по условиям используются конструкции if-then-else.
Программа может вызывать перечисленные ниже конструкции с помощбю ключевых слов, указанных в таблице 4.
- дерево синтаксического анализатора;
- поиск в таблицах (logical_table, dynamic_table);
- функции обработки пакетов;
- специальные функции;
-
сравнение силы.
Присваивание значений не допускается в конструкции program.
Таблица 4. Конструкция program (порядок выполнения).
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
program |
Задает порядок выполнения операций обработки пакета. | |
Условные конструкции |
В конструкциях с условием внутри program можно вызывать:
|
|
parse_begin(<имя узла>) |
Прохождение дерева синтаксического анализа от корневого узла. Для разных пакетов могут применяться разные деревья. | |
parse_continue(<имя узла>) |
Возврат процесса к заданному узлу дерева анализа. | |
<table>.lookup(<lookup_num>) |
Поиск в таблице с автоматическим выполнением связанных с таблицей методов. |
|
Вызовы функций обработки пакетов | ||
Вызовы специальных функций | ||
Сравнение силы | Выбор результата при поиске в нескольких таблицах. |
Пример
program mim_main () {
parse_begin (ethernet); /* Начало анализа из узла parser_node Ethernet. */
port.lookup (0); /* Поиск в таблице port. */
iif.lookup (0); /* Поиск в таблице iif. */
my_station.lookup (0); /* Поиск в таблице my_station. */
isid.lookup (0); /* Поиск в таблице isid. */
mim_isid_switch_logic1(); /* Выполнение логической функции. */
...
if (cmd_bus.do_l3) { /* Поиск по условию. */
l3_host.lookup (0);
}
...
l3_switch_logic1(); /* Вызов функции обработки пакета. */
...
next_hop.lookup (0); /* Поиск в таблице next_hop. */
do_packet_edits(); /* Функция редактирования. */
}
4.5. Конструкция синтаксического анализатора
Конструкция parser определяет:
- заголовки — упорядоченный набор полей фиксированного или переменного размера;
- группы заголовков;
- пакеты, состоящие из групп заголовков;
- связи между типами заголовков, формирующие дерево анализа.
NPL позволяет задавать базовые типы заголовков с помощью struct. Спецификация анализатора использует типы заголовков при объявлении struct для групп заголовков, а группы — при объявлении struct для пакетов. Программы NPL должны определять пакеты в форме packet.header_group.header

Рисунок 2. Заголовки, группы и пакеты.
4.5.1. Заголовок (struct)
Заголовки определяются с помощью типа данных struct, в которых поля заголовка определяются типами bit, bit-array и varbit для задания полей переменного размера, где header_length_exp служит для указания размера поля varbit. В NPL поддерживаются массивы заголовков.
Таблица 5. Конструкция заголовка (struct)
Аргументы, опции |
Описание |
---|---|
struct |
Задает новый тип заголовка. |
varbit |
При наличии полей переменного размера они задаются типом varbit с указанием максимального размера. В заголовке допускается лишь одно поле типа varbit, которое должно указываться последним. NPL не ограничивает размер таких полей. |
header_length_exp |
Для полей переменного размера задает выражение, определяющее реальный размер. В выражении можно использовать операторы + и *. Выражения следует указывать в форме var * c0 + c1, где var является полем заголовка, а c0 и c1 — константами.
Для полей фиксированного размера это поле не требуется. |
Пример
Статический заголовок
struct vlan_t { fields { bit[3] pcp; bit cfi; bit[12] vid; bit[16] ethertype; } }
Задает размер и порядок упаковки полей в заголовок пакета.
Заголовок переменного размера
struct ipv4_t { fields { bit[4] version; bit[4] hdr_len; bit[8] tos; bit[32] sa; bit[32] da; varbit[320] option; // максимальный размер поля } header_length_exp: hdr_len*4; // задает способ расчета числа байтов в заголовке
В приведенном примере option является полем переменного размера, а header_length_exp указывает способ вычисления размера заголовка. Другим примером может служить header_length_exp : (payload_len*4)+2. Вы выражении докускаются операторы + и *. В varbit[num] значение num указывает максимальный размер поля. Для каждого заголовка с полями переменного размера должен указываться атрибут header_length_exp. Структуры с несколькими полями переменного размера не поддерживаются.
4.5.2. Группа заголовков (struct)
Группы заголовков определяются с помощью типа struct и впоследствии служат для задания пакетов (4.5.3. Конструкция для пакета). Специальных опций для таких структур не поддерживается. Структура уровня группы заголовков позволяет «массовые» манипуляции и ссылки на заголовки в пакетах. Заголовки помещаются в группу в порядке, указанном NPL.
-
Группа заголовков может включать лишь struct с заголовками. Типы bit или bit-array не разрешаются.
-
Массивы групп заголовков не поддерживаются.
-
Заголовки и группы заголовков должны указываться в порядке их размещения в пакетах. Это важно!
Пример
Группа заголовков с множеством struct
struct l2_header_t { fields { bit[48] macda; bit[48] macsa; bit[16] ethertype; } } struct vlan_tag_t { fields { bit[3] pcp; bit cfi; bit[12] vid; } } struct group0_t { fields { l2_header_t l2_header; vlan_tag_t ovlan; } }
Для группы заголовков struct задает порядок размещения заголовков в группе.
Задание массива заголовков
struct group1_t { fields { mpls_t mpls[3]; // говорит о наличии трех заголовков mpls_t. } }
Элементы массива можно указывать в форме mpls[0], mpls[1], mpls[2].
4.5.3. Конструкция для пакета
Пакет состоит из групп заголовков и в NPL представляется с помощью struct. Экземпляры пакета объявляются с ключевым словом packet
packet struct-name instance-name;
Здесь объявляется пакет с именем instance-name, который описан в структуре struct-name, называемой структурой уровня пакета. Элементами этой структуры должны быть структуры групп заголовков. Структура для пакета не может содержать типы bit и bit-array, а экземпляры пакетов не могут быть массивами.
Структура уровня пакета служит для объединения групп заголовков в пакет.
Пример
Определение структуры и создание экземпляра пакета
struct macs_t { // Структура заголовка, где все элементы являются массивами битов fields { bit[48] dmac; bit[48] smac; } } struct vlan_t { // Структура заголовка fields { bit[16] tpid; bit[3] pcp; bit dei; bit[12] vid; } } struct ethertype_t { // Структура заголовка fields { bit[16] type; } } struct mpls_t { // Структура заголовка, где все элементы являются массивами битов fields { bit[20] label; bit[3] tc; bit s; bit[8] ttl; } } struct mpls_grp_t { // Структура группы заголовков fields{ mpls_t mpls[3]; } } struct ipv4_t { // Структура заголовка fields { // Определения полей IPv4 (только bit-array)... } } struct ipv6_t { // Структура заголовка fields { // Определения полей IPv6 (только bit-array)... } } struct l2_t { // Структура группы заголовков, где все элементы являются структурами fields { macs_t macs; vlan_t ctag; ethertype_t etype; } } struct l3_t { // Структура группы заголовков fields { ipv4_t ipv4; ipv6_t ipv6; } } struct ingress_packet_t { // Структура уровня пакета fields { l2_t l2; mpls_grp_t mpls_grp; l3_t l3; } } packet ingress_packet_t ing_pkt; // Указывает ingress_packet_t как структуру уровня пакета
Заголовок и поля в пакете должны указываться, как приведено ниже.
ing_pkt.l2.macs.da
Ниже показано, как должны задаваться массивы в заголовке.
ing_pkt.mpls_grp.mpls[0].label
Платформы могут вносить ограничения для доступа и изменения пакетов. Например, для разделения архитектуры Ingress и Egress могут использоваться входные и выходные пакеты, при этом запись во входные может быть отключена. Все изменения могут вноситься лишь в выходные пакеты.
4.5.4. Метаданные заголовка
С каждым заголовком связано 1 битовое поле метаданных _PRESENT, указывающее, пригоден ли конкретный экземпляр заголовка для текущего пакета. Это делает метаданные _PRESENT динамическими. При наличии заголовка во входящем пакете для флага _PRESENT устанавливается значение 1. Поле _PRESENT доступно лишь для чтения и сохраняется в течение срока жизни пакета.
Пример
struct tcp_t { fields { ... } } struct group0_t { fields { tcp_t tcp; ... } } struct packet_t { fields { group0_t group0; ... } } packet packet_t ing_pkt; program l3 () { ... if (ing_pkt.group0.tcp._PRESENT) { l2_table.lookup(0); } ... }
4.5.5. Соединения дерева анализа (parser_node)
Конструкция parser_node задает соединения экземпляров заголовков в пакетах. По сути, она определяет узлы анализа и переходы. В parser_node поддерживаются условные конструкции для перехода к следующему parser_node.
Таблица 6. Конструкция parser_node.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
parser_node |
Задает узел синтаксического анализатора и его соединения со следующими узлами. |
|
name |
Имя parser_node. |
|
root_node |
В дереве анализатора может быть лишь один корневой узел. Функция parse_begin() может указывать только один корневой узел. Для прерывания и повторного входа в дерево анализа служат parse_break и parse_continue. |
|
next_node<node> |
Задает следующий узел, с которым соединен данный parser_node. |
|
switch |
Задает оператор switch для описания условия перехода к следующему parser_node. В операторах может применяться ключевое слово mask. В операторах разрешены битовые массивы. Значения вариантов могут быть константами или константами с маской (операция AND). |
|
if/else |
Условие перехода к следующему узлу. Поддерживаются операторы сравнения == и !=, если rvalue является константой. Поддерживаются логические операции &&, ||, !. |
|
extract_fields(packet.header) |
Задает экземпляр заголовка для анализа. Текущая позиция анализа пакета выходит за пределы этого заголовка. |
|
parse_break(<node>) |
Задает выход из дерева анализа и возврат в программу. Указанный аргументом узел является следующим узлом, с которого возобновляется анализ при возврате в дерево. |
|
end_node |
Указывает последний лист в дереве. |
|
latest |
Указывает последний заголовок, проанализированный в этом parser_node. Имя latest.field может использоваться в дереве анализа. |
|
current |
Указывает текущие байты пакета для выравнивания. Например, для просмотра следующих 2 байтов в пакете следует использовать current. При вызове extract_fields указатель current выходит за пределы анализируемого заголовка. current (смещение первого бита, число выбираемых битов) |
|
default |
Задает в операторе switch вариант, применяемый при отсутствии иного подходящего. |
Пример
Задание дерева анализа
struct vlan_t { fields { bit[3] pcp; bit[1] cfi; bit[12] vid; bit[16] ethertype; } } struct l2_t { fields { bit[48] macda; bit[48] macsa; bit[16] ethertype; } } struct group1_t { fields { l2_t l2; vlan_t vlan; } } struct ing_pkt_t { fields { group1_t group1; } } parser_node start { root_node : 1; next_node ethernet; } parser_node ethernet { extract_fields(ing_pkt.group1.l2); switch (latest.ethertype) { 0x8100 : {next_node ctag}; default : {next_node ingress}; } } parser_node ctag { extract_fields(ing_pkt.group1.vlan); if (current(0,16) == r_ing_outer_tpid_0.tpid) { next_node otag; } next_node ingress; } parser_node ingress { end_node : 1; }
4.5.6. Выход и повторный вход в дерево (parse_break, parse_continue)
NPL поддерживает механизм для поиска в таблицах и выполнения иной работы с пакетами в процессе анализа заголовков. Это может требоваться в тех случаях, когда решению в узле анализатора нужен результат поиска в таблице. Поток анализатора прерывается с возвратом управления в программу, а после выполнения требуемых действий возвращается в тот же узел. Для этого служат конструкции parse_break и parse_continue.
Пример
Выполнение поиска в таблице перед анализом пакета ethernet и mpls.
program mpls_switch() { parse_begin(start); port_table.lookup(0); // узел ethernet принимает вывод из port_table, т. е. logical_bus.otpid_enable parse_continue(ethernet); // узел mpls_label принимает вывод из mpls_table, т. е. // logical_bus.mpls_table_result_type mpls_table.lookup(0); parse_continue(mpls_label); } parser_node start { root_node : 1; switch(logical_bus.rx_port_parse_ctrl) { 0x0: next_node ppd; 0x2: next_node sobmh; 0x3: parse_break(ethernet); default: next_node ingress; } } parser_node ethernet { extract_fields(ingress_pkt.outer_l2_hdr.l2); if (logical_bus.otpid_enable[3:3] && latest.ethertype == 0x8100) {next_node otag;} //0x8100 if (logical_bus.otpid_enable[2:2] && latest.ethertype == 0x8100) {next_node otag;} //0x8100 if (logical_bus.otpid_enable[1:1] && latest.ethertype == 0x8100) {next_node otag;} //0x8100 if (logical_bus.otpid_enable[0:0] && latest.ethertype == 0x8100) {next_node otag;} //0x8100 } parser_node mpls_0 { extract_fields(ingress_pkt.outer_l3_l4_hdr.mpls[0]); switch (latest.stack) { 0x0: next_node mpls_1; 0x1: parse_break(mpls_label); default: next_node ingress; } } parser_node mpls_label { extract_fields(ingress_pkt.outer_l3_l4_hdr.mpls[4]); switch (logical_bus.mpls_table_result_type) { 0x0: next_node mpls_cw; 0x1: next_node inner_ethernet; 0x2: next_node inner_l3_speculative; default: next_node ingress; } } parser_node ingress { end_node:1; }
Запись end_node:1 указывает завершение анализа.
4.6. Конструкция логической шины
Конструкции логических шин служат для определения набора полей (переменных)).
4.6.1. Определение шины
Шины создаются с помощью конструкции struct, содержащей поля и наложения. Указанный в структуре порядок полей поддерживается логической шиной.
4.6.2. Создание экземпляра шины (bus)
Для создания шины используется ключевое слово bus, а шина определяется с помощью struct. Шина может включать наложение полей, которые могут индивидуально указываться в программе NPL.
Пример
Создание экземпляра шины
struct control_bus_t { fields { bit ts_enable; bit olp_enable; bit[4] otpid_enable; } } bus control_bus_t control_id; parser_node pkt_start{ root_node : 1; next_node ethernet; } parser_node ethernet { extract_fields(ing_pkt.group0.l2); if (control_id.ts_enable == 0) { // control_id - логическая шина, // ts_enable - поле шины. if (control_id.otpid_enable != 0 ) { switch (latest.ethertype) { 0xABCD : {next_node vntag}; 0x8888 : {next_node etag}; 0x8100 : {next_node otag}; 0x9100 : {next_node itag}; 0x0000 mask 0xFC00 : {next_node llc}; default : {next_node payload}; } } } } parser_node otag{ extract_fields(ing_pkt.group0.ovlan); end_node:1; }
4.7. Конструкции логических таблиц
Конструкция логической таблицы служит для определения таблицы с ключами (keys), полями fields, key_construct, fields_assign, а также minsize и maxsize. Таблицы также имеют встроенный метод lookup().
4.7.1. Логическая таблица (logical_table)
Конструкция logical_table служит для объявления таблиц «сопоставление-действие» (СД). Это позволяет пользователю задать структур данных, которую уровень управление или уровень данных может менять. Пользователь может задать поля ключа и правила для хранения в таблице, а также механизм создания ключей с использованием полей логической шины. Кроме того, logical_table позволяет задать метод fields_assign для работы с полями.
Ключи и поля логической таблицы имеют локальную значимость. Поиск в таблицах NPL может выполняться многократно, в зависимости от возможностей целевой архитектуры.
Все объявленные логические таблицы должны вызваться конструкцией вида <имя таблицы>.lookup(lookup_num). lookup_num = 0 указывает первый поиск.
Таблица 7. Конструкция logical_table.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
logical_table |
Задает новую таблицу. |
|
table_name |
Задает имя таблицы. |
|
table_type |
Задает тип таблицы с точки зрения пользователя. Допустимы типы index, tcam, hash, alpm. Компилятор может отображать типы на разные компоненты, а целевая платформа может добавлять свои типы. |
|
keys |
Задает ключи, использцемые для доступа к логической таблице. Размер ключа задается объявлением bit. Ключи могут иметь тип bit или bit-array, тип struct не разрешен для ключей. |
|
fields |
Задает поля правил для logical_table. Размер поля задается объявлением bit. Поля могут иметь тип bit, bit-array и auto_enum, тип struct не разрешен. Тип auto_enum служит для задания множества представления данных. |
|
key_construct() |
Задает логику создания ключей таблицы с использованием приведенных ниже правил.
|
|
fields_assign() |
Метод описания функциональности обработки и назначения полей логической шины.
|
|
minsize |
Минимальный гарантированный размер. Физическая таблица должна иметь такое число элементов. |
|
maxsize |
Максимальный разрешенный размер. При разных maxsize и minsize это значение служит в основном для заполнения SDK6. Одинаковые значения minsize и maxsize считаются «размером» таблицы и компилятор должен найти физическую таблицу указанного размера. |
Пример
Определение индексной таблицы
logical_table port { table_type : index; minsize : 128; maxsize : 128; keys { bit[7] port_num; } fields { bit[1] l3_enable; bit[1] otag_enable; bit[8] src_modid; bit[12] default_vid; } key_construct() { port_num = obj_bus.port_num; } fields_assign() { if (_LOOKUP0 == 1) { cmd_bus.port_l3_enable = l3_enable; ... } } }
Определение таблицы TCAM
logical_table my_station_hit { table_type : tcam; maxsize : 512; minsize : 512; keys { bit[48] macda; bit[12] vid; bit[8] src_modid; } fields { bit[2] mpls_tunnel_type; bit local_l3_host; } key_construct() { macda = ing_pkt.l2_grp.l2.macda; vid = obj_bus.vlan_id; src_modid = obj_bus.source_logical_port; } fields_assign() { if (_LOOKUP0 == 1) { l3_cmd_bus.local_l3_host = local_l3_host; ... } } }
Вызов таблицы
program ingress { port.lookup(0); //calls port logical table if (cmd_bus.vlan_valid == 1) { my_station_hit.lookup(0); // Вызов поиска в my_station_hit первый раз my_station_hit.lookup(1); // Вызов поиска в my_station_hit второй раз } }
4.7.2. Метаданные логической таблицы
В NPL каждая логическая таблица имеет перечисленные ниже метаданные. Для каждого пакета значение метаданных присваивается с использованием ряда правил.
-
_LOOKUPx — 1-битовое значение, устанавливается при поиске в таблице для пакета.
-
_HIT_INDEXx — 32-битовое значение, указывающее строку таблицы, которой соответствует пакет. Формат _HIT_INDEXx может зависеть от целевой платформы. Должен использоваться один бит, показывающий, соответствует ли поиск действительной записи.
-
_VALID — 1-битовое значение, устанавливаемое если поиск дает действительную запись.
В именах x представляет lookup_num (0, 1 и т. д.).
Пример
logical_table table_a { ... fields_assign() { if (_LOOKUP0) { obj_bus.src_hit_index = _HIT_INDEX0; } if (_LOOKUP1) { obj_bus.dst_hit_index = _HIT_INDEX1; } } }
Как и метаданные заголовка, это повышает удобочитаемость и обеспечивает основу для инструментария.
4.7.3. Множественный поиск в таблице
NPL позволяет задать множественный поиск в одной logical_table. В этом случае могут применяться метаданные _LOOKUP0, _LOOKUP1 и т. д., чтобы различать ключи и поля, обрабатываемые в блоках key_construct() и fields_assign().
Пример
//Определение логической таблицы logical_table mac_table { table_type : hash; minsize : 64; maxsize : 64; keys { bit[48] macda; } fields { bit[16] port; bit[1] dst_discard; bit[1] src_discard; } key_construct() { if (_LOOKUP0==1) { macda = ing_pkt.l2_grp.l2.da; } if (_LOOKUP1==1) { macda = ing_pkt.l2_grp.l2.sa; } } fields_assign() { if (_LOOKUP0==1) { // Например, Entry 100 obj_bus.dst = port; obj_bus.dst_discard = dst_discard; } if (_LOOKUP1==1) { //Например, Entry 200 temp_bus.src_port = port; obj_bus.src_discard = src_discard; } } } program { if ((ing_pkt.l2_grp.l2._PRESENT) & (ing_pkt.l2_grp.vlan.vid != 0)) { // Условие поддерживается. mac_table.lookup(0); mac_table.lookup(1); } }
4.7.4. Множество типов данных (резимы размера данных)
Внутри логических таблиц поля могут упаковываться в разные форматы, которые могут требоваться по причинам размера или наложения разных данных. Это применяется для повышения эффективности.
Разработчик NPL должен задать все эти поля разных типов данных в конструкции fields{}. Например, логическая таблица NHI имеет два представления данных:
представление 1 — поля A, B, C;
представление 2 — поля A, D, E, F.
Правила NPL _VALID:
-
если логическая таблица имеет много типов данных, она будет включать 1 вхождение _VALID (_VALID = 0);
-
если некоторые поля имеют strength, они должны быть указаны в разделе _VALID=0 внутри блока fields_assign(). Вызовы strength выполняются лишь для случаев _VALID=1.
Пример
auto_enum multi_view { UC_VIEW, MC_VIEW, BC_VIEW } logical_table NHI { ... fields { bit[3] A; bit[15] B; bit[7] C; bit[10] D; bit[4] E, bit[4] F; bit[16] strength_object_G; multi_view X; // поле data_type для обозначения разных представления (auto_enum). } fields_assign() { if (_LOOKUP0 == 0) { if (_VALID == 1) { // _VALID — то же, что «попадание». if (X == UC_VIEW) { bus.A = A; bus.B = B; bus.C = C; } if (X == MC_VIEW) { bus.A = A; bus.D = D; bus.E = E; bus.F = F; } } // завершение _VALID == 1 else { // _VALID == 0 задает лишь поля data_type = 0. bus.A = 0; bus.B = 0; bus.C = 5; // пример ненулевой константы. bus.G = 0; } // завершение _VALID == 0 } if (_LOOKUP1 == 1) { ... } } }
4.8. Конструкция логического регистра
Конструкция logical_register служит для задания объекта со множеством полей и глубиной 1 (регистр). Логический регистр обеспечивает программам интерфейс для настройки элементов управления. В отличие от таблиц регистры не имеют ключей поиска. Поля могут инициализироваться во время компиляции и заполняться в процессе работы.
4.8.1. Определение одноуровневого хранилища
Конструкция logical_register задает один логический регистр со множеством полей. Результат всегда доступен для вызывающей функции, индекс не требуется. Логические регистры не могут использоваться для поддержки состояний, связанных с разными пакетами. Эти регистры содержат лишь конфигурацию уровня управления.
Регистры часто полезны в деревьях анализа, функциях и т. п. Они могут применяться в качестве констант, настраиваемых уровнем управления.
Таблица 8. Конструкция logical_register.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
logical_register |
Задает новый регистр, который может иметь произвольный размер. |
|
fields |
Задает поля данных логического регистра. Размер полей указывается с использованием типа bit. Для каждого поля должно указываться значение при сбросе. |
Пример
Определение логического регистра
// Fэта конструкция может использоваться, например, для задания значений, подобных TPID. logical_register tpid_values { fields { bit[16] tpid0 = 0x8100; bit[16] tpid1 = 0x9100; bit[16] tpid2 = 0x7100; bit[16] tpid3 = 0x8868; } }
В определении указан размер и значение каждого поля при сбросе.
4.9. Функции обработки пакетов (function)
Функции применяются в NPL для описания базовой обработки пакетов и обработки результатов синтаксического анализа, логических таблиц, special_function и других конструкций. Функции являются императивными конструкциями, которые могут преобразовывать данные и поддерживать модульность приложений. Из функций могут вызываться другие конструкции NPL.
Функции поддерживают условные операторы, операторы присваивания и комплексные операции преобразования данных на логических шинах. Для функций поддерживается вложенность. Объявления конструкций внутри функции не разрешаются.
Имеется несколько сценариев использования функций и это позволяет реализовать гибкую логику принятия решений. Например, можно декодировать результаты поиска для идентификации индивидуальных (unicast) и групповых (multicast) пакетов. Функции можно применять для извлечения данных из пакета, вызова поиска в логических таблицах, выполнения специальных функций.
Функции позволяют также организовать модульную структуру приложения. Для этого функция может включать поиск в логических таблицах, сравнение силы, вызовы динамических таблиц и т. п. Все элементы, которые могут быть заданы в конструкции program, подходят для функций. Рекомендуется создавать отдельные функции для поддержки модульности и обработки пакетов.
Целевые платформы могут ограничивать область действия и применение функций с учетом аппаратных возможностей.
Таблица 9. Конструкция function.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
function |
Задает новую функцию обработки пакетов. |
|
function_name |
Имя функции. |
|
Любые условные, арифметические и логические операторы, манипуляции с логическими шинами, поиск в logical_table, special_function, editor, strength. |
Пример
// Шина будет применяться внутри функций. struct switch_logic_t { fields { bit no_l3_switch; bit l2_same_port_drop; } } // Логические регистры доступны из функций. logical_register cpu_control { fields { bit tunnel_to_cpu = 0; // Инициализация. } } bus switch_logic_t temp; function l3_switch_logic1 () { temp.no_l3_switch = 0; if (port.l3_enable && (ingress_pkt.outer_l3_l4_hdr.ipv4._PRESENT || ingress_pkt.outer_l3_l4_hdr.ipv6._PRESENT) ) { if (obj_bus.tunnel_pkt || obj_bus.tunnel_error) { if (obj_bus.tunnel_error) { obj_bus.tunnel_decap = 0; temp.no_l3_switch = 1; if (cpu_control.tunnel_to_cpu) { obj_bus.copy_to_cpu = 1; } } else { obj_bus.tunnel_decap = 1; } } else { // Не туннельный пакет. obj_bus.tunnel_decap = 0; } } // Трассировка пакета packet_trace(temp.no_l3_switch, cpu_reason.NO_SWITCH); temp.l2_same_port_drop = obj_bus.src_prune_en && (obj_bus.l2_oif == obj_bus.l2_iif); // Отбрасывание пакета packet_drop(temp.l2_same_port_drop, drop_reason.L2_SAME_PORT_DROP, L2_SAME_PORT_DROP_STR); }
4.10. Конструкции редактирования пакетов
Конструкции для редактирования пакетов включают добавление нового заголовка (поля создаются с использованием функции), удаления и изменения заголовка (в логической шине). Измененный пакет (после редактирования) должен соответствовать одному из описанных в графе анализа пакетов, поэтому в редакторе нужно использовать имена из дерева синтаксического анализа.
Создание нового заголовка не входит в конструкцию редактора, которая должна вызываться из функции. Входные пакеты открыты лишь для записи и не могут редактироваться — все операции редактирования выполняются с выходными пакетами.
4.10.1. Добавление заголовка
Новый заголовок создается с использованием функций, после чего добавляется в пакет. Компилятор редактора распознает заголовок и будет работать с ним.
Таблица 10. Конструкция add_header.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
add_header |
Задает добавление заголовка в пакет. |
|
new_header_name |
Имя нового заголовка, совпадающее с именем в спецификации пакета. |
Пример
Добавление заголовка в пакет
Если приложению нужно добавить otag и создать его до вызова add_header(otag), можно задать
egr_pkt.group1.otag.vid = ing_pkt.itag.vid+100; // otag и itag заданы как заголовки в packet.
egr_pkt.group1.otag.pcp = obj_bus.egr_port_table_pcp; // из шины object.
egr_pkt.group1.otag.tpid = 0x9100;
add_header(egr_pkt.group1.otag);
Добавление туннельного заголовка к пакету
Если приложению нужно добавить туннельный заголовок Tunnel L2 и VLAN ID, можно задать
egr_pkt.group2.tunnel_l2.dmac = 0xff; egr_pkt.group2.tunnel_l2.smac = obj_bus.l3_interface_smac; egr_pkt.group2.tunnel_l2.vid = obj_bus.l3_next_hop_vid; add_header(egr_pkt.group2.tunnel_l2);
4.10.2. Удаление заголовка
Удаляет заголовок из стека заголовков пакета. Применяется в некоторых приложениях, таких как выход краевого коммутатора, для удаления туннельных заголовков.
Таблица 11. Конструкция delete_header.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
delete_header |
Задает удаление заголовка из пакета. Для указания заголовка используется спецификация синтаксического анализа. |
|
header_name |
Имя удаляемого заголовка, совпадающее с именем в спецификации пакета. |
Пример
Для удаление заголовка otag из пакета можно задать
delete_header(egr_pkt.group1.otag);
Это работает с экземпляром пакета, удаляя из него otag без влияния на дерево синтаксического анализа.
Для удаления группы заголовков можно использовать
delete_header(egr_pkt.group1);
Это работает с экземпляром пакета, удаляя из него группу заголовков group1 без влияния на дерево анализа.
4.10.3. Перезапись заголовка
Для некоторых протоколов при обработке пакета требуется изменять некоторые поля заголовка.
Таблица 12. Конструкция replace_header_field.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
replace_header_field |
Заменяет поле заголовка полем из лины или другим полем заголовка. |
|
dest_field |
Имя изменяемого поля заголовка. |
|
src_field |
Имя поля, используемого в качестве источника при замене (bus.field или header.field). |
Пример
Для изменения поля dscp можно использовать
replace_header_field(egr_pkt.ipv4.dscp, obj_bus.new_dscp);
4.10.4. Создание контрольной суммы
Конструкция create_checksum может использоваться только в функциях и поддерживает расчет контрольных сумм TCP и UDP.
Таблица 13. Конструкция create_checksum.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
create_checksum |
Создает контрольную сумму. |
|
checksum_field |
Задает имя поля контрольной суммы в пакете (<packet.field>). |
|
<packet_field_list> |
Упорядоченный список полей для расчета контрольной суммы. <packet._PAYLOAD> считается флагом включения данных в контрольную сумму. |
create_checksum(egress_pkt.group2.ipv4.hdr_checksum, {egress_pkt.group2.ipv4.version, egress_pkt.group2.ipv4.hdr_len, egress_pkt.group2.ipv4.tos, egress_pkt.group2.ipv4.v4_length, egress_pkt.group2.ipv4.id, egress_pkt.group2.ipv4.flags, egress_pkt.group2.ipv4.frag_offset, egress_pkt.group2.ipv4.ttl, egress_pkt.group2.ipv4.protocol, egress_pkt.group2.ipv4.sa, egress_pkt.group2.ipv4.da}); create_checksum(egress_pkt.fwd_l3_l4_hdr.udp.checksum, {egress_pkt.fwd_l3_l4_hdr.ipv4.sa, egress_pkt.fwd_l3_l4_hdr.ipv4.da, editor_dummy_bus.zero_byte, egress_pkt.fwd_l3_l4_hdr.ipv4.protocol, egress_pkt.fwd_l3_l4_hdr.udp.udp_length, egress_pkt.fwd_l3_l4_hdr.udp.src_port, egress_pkt.fwd_l3_l4_hdr.udp.dst_port, egress_pkt.fwd_l3_l4_hdr.udp.udp_length, egress_pkt.fwd_l3_l4_hdr.udp._PAYLOAD});
4.10.5. Обновление размера пакета
Конструкция update_packet_length может применяться только в функциях.
Таблица 14. Конструкция update_packet_length.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
update_packet_length |
Обновляет размер пакета. |
|
packet_length_field |
Задает имя поля размера в пакете (<packet.field>). |
|
update_type |
Задает тип обновления размера пакета:
|
|
truncate_mode |
Указывает выполнение отсечки пакета:
|
update_packet_length(egress_pkt.group2.ipv4.v4_length, 1);
Пример
Использование create_checksum и update_packet_length
function do_checksum_update() { create_checksum(egress_pkt.group2.ipv4.hdr_checksum, {egress_pkt.group2.ipv4.version, egress_pkt.group2.ipv4.hdr_len, egress_pkt.group2.ipv4.tos, egress_pkt.group2.ipv4.v4_length, egress_pkt.group2.ipv4.id, egress_pkt.group2.ipv4.flags, egress_pkt.group2.ipv4.frag_offset, egress_pkt.group2.ipv4.ttl, egress_pkt.group2.ipv4.protocol, egress_pkt.group2.ipv4.sa, egress_pkt.group2.ipv4.da}); create_checksum(egress_pkt.group4.ipv4.hdr_checksum, {egress_pkt.group4.ipv4.version, egress_pkt.group4.ipv4.hdr_len, egress_pkt.group4.ipv4.tos, egress_pkt.group4.ipv4.v4_length, egress_pkt.group4.ipv4.id, egress_pkt.group4.ipv4.flags, egress_pkt.group4.ipv4.frag_offset, egress_pkt.group4.ipv4.ttl, egress_pkt.group4.ipv4.protocol, egress_pkt.group4.ipv4.sa, egress_pkt.group4.ipv4.da}); } function do_packet_length_update() { update_packet_length(egress_pkt.group2.ipv4.v4_length, 1); update_packet_length(egress_pkt.group4.ipv4.v4_length, 1); } program app { ... do_packet_length_update(); do_checksum_update(); ... }
5. Конструкции для целевой платформы
В конвейере обработки целевой прлатформы могут быть базовые утилиты, ускорители, настраиваемые компоненты, обеспечивающие эффективную реализацию некоторых сетевых функций. NPL поддерживает конструкции для определения и вызова таких компонентов вмсте с остальными логическими функциями. Производитель платформы определяет, а разработчик прораммы NPL может вызывать из своего приложения:
-
внешние функции платформы;
-
специальные функции;
-
динамические таблицы.
Внешними функциями целевой платформы являются базовые функции целевой архитектуры. Внешние функции можно неоднократно вызывать из приложения вместе с другими конструкциями NPL. Например, внешняя функция отбрасывания пакетов может вызываться как часть поиска logical_table.
Конструкции специальных функций используются для указания ускорителей или блока IP7 целевой платформы и режимов их использования. Для специальных функций нужны особые соединения в терминах входов и выходов. Внутреннее устройство специальных функций на задается в NPL, это остается за производителем, программы NPL просто вызывают функции.
Конструкции логических таблиц служат для задания таблиц целевой платформы, применяемых в процессе работы. Производитель платформы задает базовую структуру динамических таблиц, а разработчик NPL — набор логических сигналов, позволяющих SDK целевой платформы создавать логические таблицы в процессе работы. После преобразования NPL в язык модели, такой как C++, поведение специальных и внешних функций определяется целевой платформой.
5.1. Внешние функции целевой платформы
Каждое сетевое устройство имеет набор фундаментальных функций, вызываемых многократно. Например, отбрасывание пакета, копирование в CPU или другой порт для трассировки или подсчета пакетов. Эти базовые функции могут быть связаны с logical_table, функцией или иной конструкцией NPL. Например, отбрасывание пакета является частью поиска в логической таблице, а отображение пакета (mirroring) — частью функции обработки.
NPL позволяет производителям платформ задавать такие функции как внешние. Производитель задает шаблон внешней функции с информацией, требуемой для эффективного использования оборудования.
5.1.1. Определение внешней функции
Определяемый производителем шаблон внешней функции аналогичен применяемым в других языках.
Таблица 15. Конструкция extern.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
extern <имя функции> |
Заданная производителем внешняя функция. |
|
direction |
in или out. |
|
Имя поля |
Задает поля с размером и типом. |
Пример
Определение внешней функции для отбрасывания пакетов
extern packet_drop(in bit[1] trigger, in const value, in const drop_code);
Здесь trigger указывает то или иное поле шины, которое может вызывать отбрасывание пакета. Остальные свойства связаны с процедурой отбрасывания.
5.1.2. Применение внешних функций
Разработчик программ NPL может вызывать внешние функции из приложения, помещая вызовы в logical_table или function.
Пример
logical_table packet_integrity { .... fields { bit copy_to_cpu; bit pkt_integrity_drop; } ...... fields_assign() { .... packet_drop(pkt_integrity_drop, drop_reason.PKT_INTEGRITY_CHECK_FAILED, 2); } }
5.2. Конструкция для специальных функций
5.2.1. Определение специальной функции
Конструкция special_function служит для задания интерфейса с IP-блоком цеелвой архитектуры и внутренняя функционаьность блока IP не задается в NPL. Определение интерфейса с блоком IP должно предоставляться производителем на основе использования шаблонов методов. Разработчик программы NPL вызывает блок IP из программы, используя заданные шаблоны и подходящие аргументы. Конструкция special_function является расширяемой и производитель может добавлять методы.
Таблица 16. Конструкция special_function.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
special_function |
Заданная производителем внешняя функция. |
|
special_function_name |
Задает имя IP-блока. |
|
<generic method>([in/out] [const] или [auto_enum] или [str] или [bit/varbit][size] или [list] [name]) |
Прототип метода IP-блока, задающий направление и типа аргументов. Предоставляется производителем платформы. Компилятор для платформы должен обеспечивать обработку определений и вызовов. Поддерживается множество методов. Тип varbit указывает маскируемый аргумент. |
5.2.1.1. Пример задания special_function для целевой платформы
Производитель платформы может применять разные методы для задания интерфейса с IP-блоком.
Пример
special_function flex_qos_phb { usage_mode_create(in const index, in bit[10] qos_base, in varbit[6] qos_attr, out bit[4] int_pri); usage_mode_select(in bit[6] eindex); }
5.2.2. Использование специальных функций
Программист NPL связывает блоки IP с логической функциональностью в программе NPL, используя:
-
методы из библиотеки special_function целевой платформы;
-
встроенный в NPL метод execute().
5.2.2.1. Методы специальных функций
Разработчик программ NPL использует методы special_function, предоставляемые целевой архитектурой, для задания соединений с блоками IP. Размещение вызовов этих блоков в коде NPL не отражает последовательность вызовов. Синтаксис вызова показан ниже. Тип и порядок аргументов должны совпадать с указанными в шаблоне. Аргументы метода special_function method передаются по ссылкам.
<special_function_name>.<method_name>(<arguments>)
5.2.2.2. execute()
Встроенный метод execute() не задается в конструкции special_function. Этот метод активирует блок IP и обеспечивает его относительную позицию в логической функциональности. Метод не использует аргументов.
Таблица 17. Конструкция execute().
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
execute() |
Встроенный метод активирования блока IP. |
5.2.2.3. Пример использования special_function для целевой платформы
Программист NPL может получить доступ к IP-блоку целевой платформы, используя прототипы метода special_function.
Пример
program app { ... flex_qos_phb.usage_mode_create(flex_qos_entry.QOS_MPLS_EXP_L3_TUNNEL_ECN, ing_obj_bus.mpls_exp_mapping_ptr[9:0], ing_cmd_bus.mpls_effective_exp_for_phb, ing_cmd_bus.int_pri); flex_qos_phb.usage_mode_create(flex_qos_entry.QOS_MPLS_EXP_L2_TUNNEL_ECN, ing_obj_bus.mpls_exp_mapping_ptr[9:0], ing_cmd_bus.mpls_effective_exp_for_phb1, ing_cmd_bus.int_pri1); ... flex_qos_phb.usage_mode_select(phb_select_lts_tcam_key.entry_index); flex_qos_phb.execute(); ... }
5.3. Конструкции для динамических таблиц
5.3.1. dynamic_table
Конструкция dynamic_table служит для задания логических таблиц в процессе работы и обслуживается целевой платформой. Разработчик программы NPL задает набор логических сигналов, позволяющих SDK создавать таблицы.
Определение dynamic_table предоставляет производитель платформы и оно не имеет явных входных или выходных соединений, лишь указывая SDK, как использовать динамические таблицы. Таблица может иметь множество методов для поддержки разных целей. Размер dynamic_table определяется равным 1.
Таблица 18. Конструкция dynamic_table.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
dynamic_table |
Задает динамическую таблицу. |
|
dynamic_table_name |
Имя блока динамической таблицы |
|
<generic method>([in/out] [list] [name]) |
Прототип метода, указывающий направление и список аргументов. Предоставляется производителем платформы. Компилятор для платформы должен обеспечивать обработку определений и вызовов. Поддерживается множество методов. |
5.3.1.1. Пример определения dynamic_table для целевой платформы
Производитель целевой платформы может применять приведенные ниже методы для определения интерфейса динамической таблицы.
Пример
dynamic_table ing_fp { presel_template(in list presel_menu); rule_template(in list rule_menu); action_template(out list action_menu); }
5.3.2. Использование динамических таблиц
Программист NPL использует шаблоны методов динамической таблицы для сопоставления с ней логических сигналов.
5.3.2.1. <dynamic_table_name>.<method_name>(<argument_list>)
Разработчик NPL применяет методы dynamic_table, предоставленные целевой архитектурой, для связывания логических сигналов с блоками динамической таблицы. Местоположение вызовов методов в NPL не отражает реальный порядок вызовов. Формат вызова шаблона метода имеет вид
<dynamic_table_name>.<method_name>(<argument_list>)
Аргументы метода динамической таблицы передаются ссылками.
5.3.2.2. lookup()
Встроенный метод lookup() не задается в конструкции dynamic_table и служит для задания места вызова dynamic_table.
Таблица 19. Конструкция lookup().
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
lookup() |
Встроенный метод для вызова блока динамической таблицы. |
5.3.2.3. Образец применения dynamic_table для целевой платформы
Разработчик NPL может получить доступ к блоку dynamic_table целевой платформы, используя прототипы методов dynamic_table.
Пример
program { ... ing_fp.presel_template({ ing_cmd_bus.l2_iif_opaque_ctrl_id, ing_cmd_bus.l3_iif_opaque_ctrl_id, ... }); ing_fp.rule_template({ pkt_fwd_field_bus.macda, pkt_fwd_field_bus.macsa, ... }); ing_fp.action_template({ ifp_scratch_bus.ifp_drop_action, ifp_scratch_bus.ifp_drop_code, ... }); ing_fp.lookup(); // поиск служит конструктором для dynamic_table }
6. Конструкции сравнения силы
В парадигме NPL может одновременно выполняться поиск в нескольких таблицах. Когда несколько таблиц назначает (assign) один и тот же объект, нужен механизм выбора между ними. В NPL используется механизм выбора на основе «силы» (strength), которая задается численным значением.
В NPL имеются конструкции для связывания значений силы с результатами поиска. При каждом поиске создается профиль силы для результата. Значение силы может быть статическим (для таблицы) или динамическим (для записи).
Для выбора на основе силы в NPL используется несколько конструкций:
-
сила записей логических таблиц;
-
таблица fields_assign() для указания необходимости сравнения силы;
-
функция сравнения силы.
6.1. Создание логической таблицы силы
Конструкция strength объявляет прототип для записей логической таблицы силы, которая может содержать одно или несколько полей strength. Для создания таблицы применяются конструкции struct, указываюшие поля, нужные для сравнения силы. Эта структура может включать лишь поля типа bit (но не вложенные struct). Экземпляры таблиц создаются с помощью конструкции strength, имя которой должно быть уникальным в глобальном масштабе. Записи таблицы для поиска при сравнении силы задаются конструкуиями strength_resolve.
Таблица 20. Конструкция strength.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
strength |
Создает экземпляр таблицы силы. |
|
Имя struct |
Имя структуры таблицы силы, имена полей которой представляют элементы записи таблицы силы (тип struct). |
|
Имя таблицы |
Имя таблицы силы в форме строки (string). |
6.2. Присоединение таблицы силы
В логической таблице fields_assign() следует применять конструкцию use_strength вместо оператора присваивания для задания сравнения силы. Применение конструкции use_strength задает индекс таблицы силы, служащий для выбора записи, которая будет определять значение силы для результата поиска в таблице.
Таблица 21. Конструкция use_strength.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
use_strength |
Привязывает таблицу силы к логической таблице. |
|
strength table name |
Имя таблицы силы, объявленное со структурой strength. |
|
index |
Индекс таблицы силы, которая будет применяться для сравнения результатов поиска в исходной логической таблице. Размер таблицы профиля силы определяется числом битов этого аргумента. В случае постоянных индексов размером будет число битов, требуемых для наибольшего значения. При размере B таблица будет иметь размер 2B. Индекс может быть:
|
6.3. Конструкция strength_resolve
Конструкция strength_resolve задает объект шины, который назначается с помощью сравнения силы. Она также задает (strength_list) значение силы для каждого сравниваемого поиска logical_table. Соответствующие записи (source_field_list) связывают значения силы с результатами поиска в таблице, которые нужно сравнить (индексы use_strength). Таблица с наибольшим значением силы будет предоставлять значение объекта шины.
Таблица 22. Конструкция strength_resolve.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
strength_resolve |
Задает способ назначения объекта выбранного из разных источников. |
|
destination bus.field |
Поле шины bus.field, куда передается объект после сравнения силы. |
|
destination bus.field strength |
Сила, связанная с целевым bus.field (bus.field или NULL). |
|
<strength_entry_0> |
Список свойств, описывающих первый элемент силы (см. strength_entry). |
|
<strength_entry_1> |
Список свойств, описывающих второй элемент силы (см. strength_entry). |
|
… |
||
<strength_entry_n> |
Список свойств, описывающих n—ый элемент силы (см. strength_entry). |
strength_entry
{table_lookup, user_defined_view_type, strength, source_field}
table_lookup |
Указывает поиск в таблице, где source_field имеет значение table.field (table._LOOKUP0 или table._LOOKUP1). |
user_defined_view_type |
Задает тип представления поля, заданный пользователем. Используется то же значение auto_enum, которое задано в блоке fields_assign(). Возможны значения auto_enum и NULL. |
strength |
Сила, связанная с результатом logical_table, соответствующим сравниваемому объекту (поле силы table.field). |
source_field |
Исходное поле, которое может быть назначено целевому полю (logical_table.field). |
Пример
Указание статической силы для объекта, полученного из 2 таблиц
Пусть псевдокод для сравнения силы имеет вид
if (obj_strength_profile[1].obj_k_str > obj_strength_profile[2].obj_k_str) cmd_bus.obj_k = Table_A.obj_k; else cmd_bus.obj_k = Table_B.obj_k;
Рисунок 3. Сила при использовании таблиц со статическим индексированием.
Конструкция будет иметь вид
struct cmd_bus_t { fields { bit[8] obj_k; } } struct obj_strength_t { fields { bit[4] obj_k_str; } bus cmd_bus_t cmd_bus; strength obj_strength_t obj_strength_profile; // таблицы профилей силы logical_table Table_A { ... fields { bit[8] obj_k; } fields_assign() { use_strength(obj_strength_profile, 1); // ссылка на запись таблицы профилей силы } } logical_table Table_B { ... fields { bit[8] obj_k; } fields_assign() { use_strength(obj_strength_profile, 2); // ссылка на запись таблицы профилей силы } } program app { ... strength_resolve(cmd_bus.obj_k, NULL, { Table_A._LOOKUP0, NULL, obj_strength_profile.obj_k_str, Table_A.obj_k}, { Table_B._LOOKUP0, NULL, obj_strength_profile.obj_k_str, Table_B.obj_k}); ... }
Указание динамической силы для объекта, полученного из 2 таблиц
Пусть псевдокод для сравнения силы имеет вид
if (obj_strength_profile[index_A].obj_k_str > obj_strength_profile[index_B].obj_k_str) cmd_bus.obj_k = Table_A.obj_k; else cmd_bus.obj_k = Table_B.obj_k;
Рисунок 4. Сила при использовании таблиц со динамическим индексированием.
Конструкция будет иметь вид
struct cmd_bus_t { fields { bit[8] obj_k; } } struct obj_strength_t { fields { bit[4] obj_k_str; } } bus cmd_bus_t cmd_bus; strength obj_strength_t obj_strength_profile; logical_table Table_A { ... fields { bit[8] obj_k; bit[5] strength_index; } fields_assign() { use_strength(obj_strength_profile, strength_index); } } logical_table Table_B { ... fields { bit[8] obj_k; bit[5] strength_index; } fields_assign() { use_strength(obj_strength_profile, strength_index); } } program app { ... strength_resolve(cmd_bus.obj_k, NULL, { Table_A._LOOKUP0, NULL, obj_strength_profile.obj_k_str, Table_A.obj_k}, { Table_B._LOOKUP0, NULL, obj_strength_profile.obj_k_str, Table_B.obj_k}); ... }
Указание силы для объекта, полученного из таблицы и шины
Пусть псевдокод для сравнения силы имеет вид
if (obj_strength_profile[a_strength_index].obj_k_str > cmd_bus.obj_k_str) cmd_bus.obj_k = Table_A.obj_k; else cmd_bus.obj_k = cmd_bus.obj_k;
Рисунок 5. Сила при использовании таблицы и шины.
Конструкция будет иметь вид
struct cmd_bus_t { fields { bit[8] obj_k; bit[4] obj_k_str; } } struct obj_strength_t { fields { bit[4] obj_k_str; } } bus cmd_bus_t cmd_bus; strength obj_strength_t obj_strength_profile; logical_table Table_A { ... fields { bit[8] obj_k; bit[3] a_strength_index; } fields_assign() { use_strength(obj_strength_profile, a_strength_index); } } program () { strength_resolve(cmd_bus.obj_k, cmd_bus.obj_k_str, {Table_A._LOOKUP0, NULL, obj_strength_profile.obj_k_str, Table_A.obj_k}); }
Указание силы при использовании двух таблиц с множественным поиском
struct obj_bus_t { fields { bit[12] dst_vlan; bit[12] src_vlan; bit[11] dst_vfi; bit[11] src_vfi; } } struct cmd_bus_t { fields { bit dst_discard; bit src_discard; } } struct UAT_strength_profile_t { fields { bit[4] obj_src_discard_str; bit[4] obj_K_str; } } strength UAT_strength_profile_t UAT_strength_profile; bus obj_bus_t obj_bus; bus cmd_bus_t cmd_bus; // множество поисков logical_table Table_A { ... field { bit[12] vlan; bit discard; // поле сравнения силы } fields_assign() { if (_LOOKUP0) obj_bus.dst_vlan = vlan; if (_LOOKUP1) obj_bus.src_vlan = vlan; use_strength(UAT_strength_profile, 10); } } // один поиск, не нужно добавлять _LOOKUP0 в fields_assign(). logical_table Table_C { ... field { bit[11] vfi; bit discard; // поле сравнения силы } fields_assign() { obj_bus.dst_vfi = vfi; } } // То же, что Table_A, но с заданной ниже логикой силы logical_table Table_B { ... field { bit[11] vfi; bit discard; // поле сравнения силы } fields_assign() { if (_LOOKUP0) { obj_bus.dst_vfi = vfi; } if (_LOOKUP1) { obj_bus.src_vfi = vfi; } use_strength(UAT_strength_profile, 20); } } program () { ... // два поиска Table_A.lookup(0); Table_A.lookup(1); // один поиск Table_C.lookup(0); // два поиска Table_B.lookup(0); Table_B.lookup(1); strength_resolve(cmd_bus.src_discard, NULL, {Table_A._LOOKUP1, NULL, UAT_strength_profile.obj_src_discard_str, Table_A.discard}, {Table_B._LOOKUP1, NULL, UAT_strength_profile.obj_src_discard_str, Table_B.discard}); strength_resolve(cmd_bus.dst_discard, NULL, {Table_A._LOOKUP0, NULL, UAT_strength_profile.obj_K_str, Table_A.discard}, {Table_B._LOOKUP0, NULL, UAT_strength_profile.obj_K_str, Table_B.discard}); ... }
6.4. Сравнение силы с помощью функции
Конструкция function может также применяться для сравнения силы при последовательном обращении к таблицам. При компиляции может быть выбрана иная схема отображения.
7. Базовые конструкции
7.1. Атрибуты NPL
Атрибуты NPL служат для передачи намерений программы NPL в файлы yaml, которые будут доступны для извлечения соответствующей информации. Атрибуты помещаются в «скобки» <! и !>. Компилятор не пытаяется проверять содержимое атрибутов NPL.
7.1.1. Позиционные атрибуты
Позиционные атрибуты служат для приклепления документации к коду NPL с целью описания различных элементов. Это отличается от комментариев (// и /* */), поддерживаемых в NPL, хотя те и другие не влияют на код и компиляцию.
Атрибуты NPL поддерживаются для перечисленных ниже элементов:
-
logical_table — ключи, поля;
-
logical_register — поля;
-
шина (struct) — поля, наложения;
-
заголовок пакета (struct) — поля;
-
special_function и dynamic_table.
Компляторы могут распространять атрибуты NPL в выходные файлы для целевой платформы. Например, целевой компилятор может распространить документацию logical_table в выходной файл Regsfile или Map.
Таблица 23. Позиционные атрибуты.
Дескриптор |
Назначение |
Описание |
---|---|---|
REGSFILE, DESC: <desc> |
logical_regsfile.yml header.yml |
Применимы к logical_table, logical_table.field, logical_table.key, logical_register, logical_register.field, struct, (bus/header) struct.field. (bus/header) Атрибут <desc> применяется как TABLE <tbl> DESC, TABLE <tbl.fld> DESC, REGISTER <reg> DESC, REGISTER <reg.fld> DESC, FORMAT <struct> DESC, FORMAT <struct.fld> DESC, HEADER <struct> DESC, HEADER <struct.fld> DESC. |
REGSFILE, ENCODING: <enum> |
logical_regsfile.yml |
Применимы к logical_table.field. Атрибут <enum> применяется как поле таблицы, ENCODINGS |
REGSFILE, ENCODING: DESC: enum.field = <desc> |
logical_regsfile.yml |
Применимы к logical_table.field, enum.field. Атрибут <desc> применяется как поле |
REGSFILE, FIELD_NAME: <field> |
logical_sftblfile.yml |
Применимы к dynamic_table.arg. Атрибут <field> применяется как dyamic_table.field |
Пример (REGSFILE, DESC: <desc>)
Код NPL
<!(REGSFILE, DESC: "This table is looked up using Layer 2 incoming interface packet was received on. This table provides incoming Layer 2 interface attributes for the packet.") !> logical_table ing_l2_iif_table { ... fields { <!(REGSFILE, DESC: "If set, IPV6 Tunnel is enabled on this interface.") !> bit ipv6_tunnel_enable; } }
Представление Regsfile
TABLE = { ing_l2_iif_table: DESC: |- This table is looked up using Layer 2 incoming interface packet was received on. This table provides incoming Layer 2 interface attributes for the packet. FIELDS: ipv6_tunnel_enable: DESC: |- If set, IPV6 Tunnel is enabled on this interface. MAXBIT: 13 MINBIT: 13 ORDER: 4 TAG: data WIDTH: 1
Пример (REGSFILE, ENCODING: <enum>)/(REGSFILE, ENCODING: DESC: enum.field = <desc>)
Код NPL
enum pvlan_port { PVLAN_PROMISCUOUS_PORT = 0, PVLAN_COMMUNITY_PORT = 1, PVLAN_ISOLATED_PORT = 2 } logical_table ing_vfi_table { ... fields { <!REGSFILE, ENCODING: pvlan_port !> ! <!REGSFILE, ENCODING: DESC: pvlan_port.PVLAN_PROMISCUOUS_PORT = Pvlan Promiscuous port !> bit[FIELD_2_WD] src_pvlan_port_type; } }
Представление Regsfile
ing_vfi_table: FIELDS: src_pvlan_port_type: ENCODINGS: pvlan_port__PVLAN_PROMISCUOUS_PORT: DESC: |- Pvlan Promiscuous port VALUE: 0 pvlan_port__PVLAN_COMMUNITY_PORT: DESC: ‘’ VALUE: 1 pvlan_port.PVLAN_ISOLATED_PORT DESC: ‘’ VALUE: 2
Пример (REGSFILE, FIELD_NAME: <field>)
Код NPL
dynamic_table egr_flex_ctr { presel_template (in list presel_menu); object_template (in list object_menu); } egr_flex_ctr.presel_template( { <!REGSFILE, FIELD_NAME: "mirror_pkt_ctrl_0" !> egr_cmd_bus.mirror_pkt_ctrl });
Представление Regsfile
dt_egr_flex_ctr_presel_template: FIELDS: mirror_pkt_ctrl_0: BUS_SELECT_WIDTH: 4 DESC: |- Input - egr_cmd_bus_mirror_pkt_ctrl MAXBIT: 65 MINBIT: 2 ORDER: 2 TAG: bus_select WIDTH: 64
7.1.2. Непозиционные атрибуты
Непозиционные атрибуты позволяют разработчику NPL полностью передать свои намерения. Все такие атрибуты собираются в выходном файле IFILE (Intent File) формата yaml. Другие атрибуты в IFILE не включаются. В файле атрибуты группируются по указанной функциональности, которую они поддерживают. Программист NPL отвечает за указание формата содержимого непозиционных атрибутов, а потребитель таких атрибутов — за их анализ и преобразование в желаемый формат, а также проверку атрибутов. Потребитель IFILE должен создать утилиту для приема выхода компилятора и преобразования его в желаемый формат с проверкой.
Непозиционные атрибуты NPL передаются в IFILE на основе описаний.
Таблица 24. Непозиционные атрибуты.
Дескриптор |
Назначение |
Описание |
---|---|---|
(IFILE, <blk>: <code>) |
ifile.yml |
Поле <code> добавляется к блоку <blk> в файле IFILE. |
7.1.2.1. Инициализация
Программист NPL может указать использование логических таблиц, таблиц силы и других элементов.
Пример
Код NPL
// назначение логической таблицы NPL
<! IFILE, INIT: ”lt ing_l3_next_hop_1_table nhop_index_1=0 dvp=0x0 l3_oif_1=0x0” !>
// назначение физического ресурса по потребности
<! IFILE, INIT: ”pt IFTA150_SBR_PROFILE_TABLE_0_INDEX=10 strength=0x5” !>
// назначение символьного элемента (в будущем)
<! IFILE, INIT: ”lt _SYMBOL=TIMESTAMP _INDEX=10 data=0x5” !>
// использование перечисляемого NPL
<! IFILE, INIT: ”pt EPOST_FMT_AUX_BOTP_IN_DATA mtu_drop=NPL_EGR_MTU_DROP_ENUM” !>
Выходной IFILE
INIT: 0: |- lt ing_l3_next_hop_1_table nhop_index_1=0 dvp=0x0 l3_oif_1=0x0 1: |- pt IFTA150_SBR_PROFILE_TABLE_0_INDEX=10 strength=0x5 2: |- lt _SYMBOL=TIMESTAMP _INDEX=10 data=0x5 3: |- pt EPOST_FMT_AUX_BOTP_IN_DATA mtu_drop=NPL_EGR_MTU_DROP_ENUM
7.1.2.2. Relational
Программист NPL может указать дополнительную функциональность, относящуюся к символам или вызовам NPL.
Пример
Код NPL
<! IFILE, REL: ”ecmp_level0:npl_ecmp_level0_member_table” !>
Выходной IFILE
REL: 0: |- ecmp_level0:npl_ecmp_level0_member_table
7.2. Конструкции препроцессора
7.2.1. #include
Директива #include служит для включения исходного кода NPL в другую программу NPL.
#include "bus.npl" // файл из того же каталога #include "../lib/header.npl" // файл из другого каталога
7.2.2. #if — #endif
NPL поддерживает условную компиляцию в стиле C/C++.
#ifdef XYZ
7.2.3. #define
NPL поддерживает макросы в стиле C/C++ для переменных, но без параметризации. Определения задаются заглавными буквами.
#define CPU_PORT 5 #define MAC_ADDR_BCAST 0xffffffffffff
7.3. Комментарии
NPL поддерживает 2 варианта комментариев:
-
многострочные с использованием символов /* и */. Такие комментарии можно включать в строку (in-line);
-
однострочные от символов // до конца строки.
7.4. print
NPL использует конструкцию print для вывода значений переменных в программе. Команда print транслируется в модель Behavioral C. Это не имеет значения с точки зрения компиляции. Вызов print в модели C выполняется в том же порядке, что и в программе NPL. Печатаются только поля (не struct). Команда print работает аналогично C printf.
print("Value of the SVP is %d, VFI is %d\n", obj_bus.svp, obj_bus.vfi);
8. Приложение A. Пример конвейера
Пример конвейера в коммутаторе показан на рисунке. Язык NPL может поддерживать разную архитектуру.
Parser задает и изменяет поведение аппаратного блока синтаксического анализа (HW Parser Block).
Match Action — логические таблицы и регистры NPL, задающие и изменяющие поведение блоков «сопоставление-действие» (Match Action Block).
Target IP — блок IP, относящийся к производителю платформы и представленный конструкцией special_function.
Processing Unit — базовый элемент обработки на целевой платформе, поведение которого задают и изменяют функции NPL и сравнение силы.
Editor — редактор NPL, определяющий поведение блока редактирования.
Целевая платформа может использовать несколько экземпляров этих блоков в произвольном порядке.
9. Приложение B. Рекомендации по использованию
9.1. struct в заголовках
Объекты верхнего уровня (например, logical_table, logical_register, enum) не могут быть частью struct. NPL поддерживает вложенные структуры с учетом приведенных ниже ограничений.
Таблица 25. Использование struct.
Применение |
bit/bit[n] |
overlay |
struct |
Вложенные struct |
---|---|---|---|---|
Тип header |
+ |
— |
— |
— |
Тип header_group |
— |
— |
+ |
— |
Пакет |
— |
— |
+ |
— |
Шина |
+ |
+ |
+ |
+ |
Таблица 26. Ссылки на struct в пакетах.
Действительная конструкция |
Возможно |
Описание ссылки |
---|---|---|
packet.struct.struct.field |
+ |
packet.group.header.field |
packet.struct.struct |
+ |
packet.group.header |
packet.struct.field |
— |
Нет группы/заголовка |
packet.field |
— |
Пакет должен иметь группу/заголовок |
Нет пакета |
— |
9.2. Функции
Функции с аргументами не поддерживаются. Функции должны работать с логическими шинами и данными пакетов.
9.3. Правила наложения
Наложения могут применяться во многих конструкциях и должны следовать приведенным ниже правилам.
-
Поля наложения могут быть заданы для базовых полей типа bit и bit[n].
-
Поля наложения могут быть заданы для базовых полей типа struct (т. е., полей, заданных как struct в шине). Однако для struct не разрешено частичное наложение.
-
Наложения могут перекрываться.
-
Не допускается задание наложений для других полей наложения.
-
Наложенные поля не могут иметь тип struct.
9.4. «Нарезка» битовых массивов
NPL поддерживает «нарезку» (диапазон) битовых массивов (bit-array) в назначениях, уравнениях, специальных функциях. Диапазоны битов можно указывать в обеих частях (lvalue и rvalue) уравнений.
Пример
a = b[7:4]; if (b[5:3])... obj_bus.a[5:4] = ing_pkt.ipv4.ecn; // в основном функции и действия a = b[0:0]; // доступ к одному биту с указанием диапазона 1 бит
Варианты применения
-
if (a[5:3]) // корректно
-
a[5:3] = b[5:3]; // корректно
9.5. Правила конкатенации
-
При назначении конкатенация разрешена лишь в правой стороне уравнений. Обычно она применяется для полей varbit.
a = b<>c;
-
Конкатенация не разрешена в левой части уравнения.
b<>c = a[5:0];
-
Конкатенация разрешена для однотипных полей (например, struct<>struct).
two_mpls_hdrs = mpls_hdr_0<>mpls_hdr_1;
-
Конкатенация разрешена для разнотипных полей (например, struct<>bit).
new_mpls_hdr = mpls_hdr_0<>c[3:0];
-
Конкатенация разрешена для частей полей (например, da[5:0]<>sa[5:3]).
a[5:0] = b[3:2]<>c[3:0];
-
При сравнении со значением регистра поддерживается лишь оператор ==.
10. Приложение C. Зарезервированные слова NPL
fields_assign |
add_header |
auto_enum |
bit |
bus |
create_checksum |
default |
delete_header |
define |
dynamic_table |
else |
enum |
extract_fields |
fields |
function |
hash |
header_length_exp |
if |
index |
keys |
latest |
logical_register |
logical_table |
mask |
maxsize |
minsize |
next_node |
NPL_PRAGMA |
overlays |
packet |
keys_construct |
parse_break |
parse_continue |
parse_begin |
end_node |
parser_node |
|
program |
replace_header_field |
root_node |
special_function |
strength |
strength_resolve |
struct |
switch |
table_type |
tcam |
update_packet_length |
use_strength |
varbit |
_HIT_INDEXx |
_LOOKUPx |
_PRESENT |
_VALID |
extern |
true |
false |
11. Приложение D. Грамматика NPL
В этом приложении описана грамматика NPL с использованием нотации yacc.
Лексер маркирует идентификаторы (ID) для заданных пользователем имен регулярным выражением [A-Za-z_][\w_]*. Десятичные константы должны быть натуральными числами. Шестнадцатеричные константы задаются в обычной форме (например, 0x0f, 0X0f, 0x0F). Константы размера подобны шестнадцатеричным константам с размерами в стиле printf (например, 8×00). Строковые константы должны заключаться в двойные кавычки и не содержать символов новой строки (например, “foo.bar”).
Идентификаторы
primary_types: constant | identifier |STR_CONST /* r'\"([^\\\n]|(\\.))*?\"' */ constant : |DEC_CONST /* ([0-9][0-9]*) */ |HEX_CONST /* (0[xX][0-9A-Fa-f]+) */ identifier : ID dir : IN | OUT
Объявления
npl_node : npl_declaration_specifier | empty npl_declaration_specifier : npl_declaration | npl_declaration_specifier npl_declaration npl_declaration : struct_definition_specifier |packet_definition_specifier |strength_definition_specifier |bus_definition_specifier |sp_definition_specifier |function_definition_specifier |special_func_defintion_specifier |enum_defintiion_specifier |program_definition_specifier |table_definition_specifier |register_definition_specifier |parsernode_definition_specifier |print_command |generic_block array_access_format : ‘[’ postfix_expression ‘]’ range_access_format : ‘[’ postfix_expression ‘:’postfix_expression ‘]’ declaration_expn : BIT identifier ‘;’ | BIT array_access_format identifier ‘;’ | VARBIT array_access_format identifier ‘;’ | identifier identifier ‘;’ | identifier identifier array_access_format ‘;’ | BIT array_access_format identifier ‘==’ constant ‘;’ field_declarator : :FIELDS ‘{’ field_declaration_list ‘}’ field_declaration_list : declaration_expn | field_declaration_list declaration_expn key_declarator : KEYS ‘{’ field_declaration_list ‘}’
Наложение
overlay_declarator : OVERLAYS ‘{’ overlay_declaration_list ‘}’ overlay_declaration_list: overlay_expression : | overlay_declaration_list overlay_expression overlay_expression : identifier ‘:’ concat_format_list ‘;’
Конкатенация сигналов
concat_format_list : concat_format | concat_format_list CONCAT concat_format concat_format : identifier | identifier range_access_format
Структура
struct_definition_specifier : STRUCT identifier ‘{’ struct_body ‘}’ | STRUCT identifier ‘{’field_declaration_list ‘}’ | STRUCT ‘{’struct_body‘}’ struct_body : field_declarator header_len_opt | struct_body overlay_declarator header_len_opt : HEADER_LENGTH_EXP ‘:’ expression_statement | empty
Перечисление
enum_defintiion_specifier : ENUM postfix_expression args_list_format
Объявление шины
bus_definition_specifier : BUS identifier identifier ‘;’
Объявление таблицы
table_definition_specifier : LOGICAL_TABLE identifier ‘{’ table_body_block ‘}’ table_type table_body_block : table_body | table_body_block table_body table_body : TABLE_TYPE ‘:’ table_type | key_declarator | field_declarator | KEY_CONSTRUCT key_construct_definition_block | FIELDS_ASSIGN fields_assign_definition_block | MAXSIZE ‘:’ constant ‘;’ | MINSIZE ‘:’ constant ‘;’ table_type : INDEX ‘;’ | HASH ‘;’ | TCAM ‘;’ | ALPM ‘;’ table_keys_list : table_keys_expression | table_keys_list table_keys_expression table_keys_expression : postfix_expression ‘;’ key_construct_definition_block : ‘{’ generic_statement_list ‘}’ fields_assign_definition_block : ‘{’ generic_statement_list ‘}’
Объявление регистра
register_definition_specifier : LOGICAL_REGISTER identifier ‘{’ field_declarator ‘}’
Объявление пакета
packet_definition_specifier : packet_instance packet_instance : PACKET identifier identifier ‘;’
Strength
strength_definition_specifier : strength_instance strength_instance : STRENGTH identifier identifier ‘;’
Блок операторов
generic_statement_list : generic_block | generic_statement_list generic_block generic_block : statement statement : expression_statement | select_statement | compound_statement | label_statement | header_command | parser_statement | pragma_call compound_statement : ‘{’ generic_statement_list ‘}’
Условные операторы
select_statement : IF ‘(’ expression ‘)’ statement ELSE statement | IF ‘(’ expression ‘)’ statement | SWITCH ‘(’ expression ‘)’ statement label_statement : postfix_expression ‘:’ statement | DEFAULT ‘:’ statement | constant MASK constant ‘:’ next_node
Выражения
expression_statement : expression ‘;’ expression : assignment_expression | lookup_statement | parse_init | function_call assignment_expression : generic_expression | generic_expression assignment_operator assignment_expression generic_expression : binary_expression binary_expression : unary_expression | function_call | binary_expression ‘!=’ binary_expression | binary_expression ‘==’ binary_expression | binary_expression ‘&’ binary_expression | binary_expression ‘<’ binary_expression | binary_expression ‘<=’ binary_expression | binary_expression ‘>=’ binary_expression | binary_expression ‘>’ binary_expression | binary_expression ‘|’ binary_expression | binary_expression ‘^’ binary_expression | binary_expression ‘&&’ binary_expression | binary_expression ‘||’ binary_expression | binary_expression ‘<<’ binary_expression | binary_expression ‘>>’ binary_expression | binary_expression ‘*’ binary_expression | binary_expression ‘+’ binary_expression | binary_expression ‘-’ binary_expression | binary_expression ‘/’ binary_expression | binary_expression ‘%’ binary_expression | binary_expression ‘<>’ binary_expression unary_expression : unary_operator unary_expression | args_format_specifier | parser_access_latest unary_operator : ‘~’ | ‘!’ | ‘|’ | ‘&’ assignment_operator : ‘==’ primary_expression : ‘(’ expression‘)’ primary_expression : primary_types | metainfo | header_position | profile_type postfix_expression : primary_expression | postfix_expression ‘.’ identifier | postfix_expression ‘.’ metainfo | postfix_expression array_access_format | postfix_expression range_access_format
Программа
program_definition_specifier : PROGRAM postfix_expression ‘{’ generic_statement_list ‘}’ | PROGRAM postfix_expression ‘{’ ‘}’
Синтаксический анализатор
parsernode_definition_specifier : PARSER_NODE identifier ‘{’ generic_statement_list ‘}’ parser_statement : next_node | root_node | parsing_done | parser_field_extract root_node : ROOT_NODE ‘:’ constant ‘;’ next_node : NEXT_NODE identifier ‘;’ parse_init : PARSE_BEGIN ‘(’ postfix_expression ‘)’ parsing_done : END_NODE’:’ constant‘;’ parser_field_extract : EXTRACT_FIELDS ‘(’ postfix_expression ‘)’ ‘;’ parser_access_latest : LATEST ‘.’ postfix_expression
Обновление заголовков пакета
header_command : CREATE_CHECKSUM ‘(’ args_format_specifier ‘)’ ‘;’ | UPDATE_PACKET_LENGTH ‘(’ args_format_specifier ‘)’ ‘;’ | ADD_HEADER ‘(’ postfix_expression ‘)’ ‘;’ | DELETE_HEADER ‘(’ postfix_expression ‘)’ ‘;’ | COPY_HEADER ‘(’ postfix_expression ‘,’ postfix_expression ‘)’ ‘;’ | REPLACE_HEADER_FIELD ‘(’ postfix_expression ‘,’ postfix_expression ‘)’ ‘;’
Поиск в таблицах
lookup_statement : LOOKUP args_access_format
Функции
function_call : postfix_expression ‘(’ ‘)’ | postfix_expression args_access_format function_definition_specifier : FUNCTION postfix_expression ‘(’ func_def_args ‘)’ \ ‘{’ function_code_block ‘}’ func_def_args : args_format_specifier | empty function_code_block : statement | function_code_block statement
Специальные функции
sp_definition_specifier : SFC postfix_expression postfix_expression ‘;’ special_func_defintion_specifier : SPECIAL_FUNCTION postfix_expression ‘{’ special_func_def_list‘}’ special_func_def_list : special_func_def | special_func_def_list special_func_def special_func_def : function_call ‘;’
Аргументы функций
args_access_format : ‘(’ args_format_specifier ‘)’ args_type_specifier : dir LIST postfix_expression | dir STR postfix_expression args_format_specifier : args_type_specifier | args_format_specifier ‘,’ args_type_specifier | args_list_format | args_format_specifier ‘,’ args_list_format | postfix_expression | args_format_specifier ‘,’ postfix_expression | args_size_dir | args_format_specifier ‘,’ args_size_dir args_list_format : ‘{’ args_format_specifier ‘}’ | ‘{’ ‘}’ args_def_list_format : ‘{’ args_size_multi ‘}’ args_size_dir : dir args_def_list_format | dir args_size args_size_multi : args_size | args_size_multi ‘,’ args_size args_size : BIT postfix_expression | BIT array_access_format postfix_expression
Команда print
print_command : PRINTLN ‘(’ STR_CONST ‘)’
Pragma
pragma_call : directive NPL_PRAGMA pragma_access_format directive : PRAGMA pragma_access_format : ‘(’ postfix_expression ‘,’ pragma_format_specifier ‘)’ pragma_format_specifier : pragma_format_specifier ‘,’ \ | postfix_expression ‘:’ postfix_expression | postfix_expression
12. Приложение E. Директивы (@NPL_PRAGMA)
В помощь компиляторам можно задавать в прикладных программах директивы. Компиляторы FE и BE используют эти директивы для размещения и других функций. Директивы не являются частью NPL, однако для лучшего понимания здесь описан синтаксис директив и даны примеры использования.
12.1. Директивы
Директивы помогают задать желаемое поведение, которое может зависеть от оборудования. Это помогает при отображениях BE. Для директив действует ряд правил:
-
директивы можно задавать в логическом файле NPL или отдельном файле;
-
с директивами не связано позиционирования;
-
директива должна начинаться с новой строки;
-
следует использовать ключевое слово null, если объект не связан с директивой или для директивы нужно несколько объектов.
Синтаксис и ключевое слово для директив имеют вид
@NPL_PRAGMA(object_name, property_name:property_value);
Пример
Задание директивы bus_type
Если в приложении пользователь хочет определить фиксированную шину, это может иметь вид
bus mpls_fixed_bus_s mpls_fixed_bus; @NPL_PRAGMA(mpls_fixed_bus, bus_type:ing_obj_fixed);
Задание директивы mapping
Для отображения конкретной таблицы, функции, sfc или bus_field на определенный аппаратный блок можно задать
function vlan_assign_functions() @NPL_PRAGMA(vlan_assign_functions,mapping:"hw_proc_block_20")
13. Примеры внешних функций
Отбрасывание пакета
Для отбрасывания пакетов применяется внешняя функция packet_drop.
Таблица 27. packet_drop.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
packet_drop |
Отбрасывает пакет. |
|
bus.field_name |
Задает имя поля, при установке которого пакет должен отбрасываться (<bus.field>). В logical_table fields_assign() это должно быть <table_field>. |
|
drop_code |
Задает константу, связанную с причиной отбрасывания (constant или enum). Допустимы значения от 0 до 255, 0 означает отсутствие кода. |
|
strength |
Задает приоритет или силу, связанные с отбрасыванием (константа или поле регистра). |
packet_drop( <bus.field_name>, // имя сигнала, с которым пользователь хочет связать отбрасывание <drop_code>, // код причины отбрасывания <strength> // приоритет причины отбрасывания );
Трассировка пакетов
Для трассировки пакетов применяется внешняя функция packet_trace.
Таблица 28. packet_trace.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
packet_trace |
Трассировка пакета. |
|
bus.field_name |
Задает имя поля, установка которого активизирует трассировку пакета (<bus.field>). В logical_table fields_assign() это должно быть <table_field>. |
|
trace_code |
Задает константу, связанную с трассировкой (constant или enum). Допустимы значения от 0 до 47, 0 означает отсутствие кода. |
packet_trace(
<bus.field_name>, // имя сигнала, с которым пользователь хочет связать трассировку
<trace_code> // код операции трассировки
);
Подсчет пакетов
Для подсчета пакетов служит внешняя функция packet_count.
Таблица 29. packet_count.
Конструкция |
Аргументы, опции |
Описание |
---|---|---|
packet_count |
Подсчет пакетов. |
|
bus.field_name |
Задает имя поля, установка которого активизирует подсчет пакетов (<bus.field>). В logical_table fields_assign() это должно быть 0 или 1 |
|
counter_id |
Задает идентификатор счетчика (constant или enum) и может принимать значение от 1 до 63. В logical_table fields_assign() это должно быть <table_field>. |
packet_count(
<bus.field_name>, // имя сигнала, с которым пользователь хочет связать подсчет
<counter_id> // идентификатор счетчика
);
Пример
Использование packet_drop, packet_trace, packet_count
Ниже приведен пример использования count, trace и drop, выполняющий ряд задач:
-
отбрасывание всех пакетов IPV4 с нулевым значением 0 с использованием drop_code 11 и priority 5;
-
трассировка всех пакетов IPV4 с ttl = 1;
-
подсчет всех пакетов IPV4 с ttl = 2
struct cond_bus_s { bit ttl_0; bit ttl_1; bit ttl_2; } bus cond_bus_s cond_bus; #define TTL0 11 function func_ttl_proc () { if (header_ipv4.ttl == 0) { cond_bus.ttl_0 = 1; } if (header_ipv4.ttl == 1) { cond_bus.ttl_1 = 1; } if (header_ipv4.ttl == 2) { cond_bus.ttl_2 = 1; } packet_drop(cond_bus.ttl_0, TTL0, 5); packet_trace(cond_bus.ttl_1, 3); packet_count(cond_bus.ttl_2, 1); } program ipv4() { ... func_ttl_proc(); ... }
Перевод на русский язык
Николай Малых