RFC 8944 A YANG Data Model for Layer 2 Network Topologies

image_print
Internet Engineering Task Force (IETF)                           J. Dong
Request for Comments: 8944                                        X. Wei
Category: Standards Track                                          Q. Wu
ISSN: 2070-1721                                                   Huawei
                                                            M. Boucadair
                                                                  Orange
                                                                  A. Liu
                                                                  Tecent
                                                           November 2020

A YANG Data Model for Layer 2 Network Topologies

Модель YANG для сетевой топологии канального уровня

PDF

Аннотация

This document defines a YANG data model for Layer 2 network topologies. In particular, this data model augments the generic network and network topology data models with topology attributes that are specific to Layer 2.

Статус документа

Документ относится к категории Internet Standards Track.

Документ является результатом работы IETF1 и представляет согласованный взгляд сообщества IETF. Документ прошел открытое обсуждение и был одобрен для публикации IESG2. Дополнительную информацию о стандартах Internet можно найти в разделе 2 в RFC 7841.

Информацию о текущем статусе документа, ошибках и способах обратной связи можно найти по ссылке https://www.rfc-editor.org/info/rfc8944.

Авторские права

Авторские права (Copyright (c) 2020) принадлежат IETF Trust и лицам, указанным в качестве авторов документа. Все права защищены.

Этот документ является субъектом прав и ограничений, перечисленных в BCP 78 и IETF Trust Legal Provisions и относящихся к документам IETF (http://trustee.ietf.org/license-info), на момент публикации данного документа. Прочтите упомянутые документы внимательно, поскольку в них описаны права и ограничения, относящиеся к данному документу. Фрагменты программного кода, включенные в этот документ, распространяются в соответствии с упрощенной лицензией BSD, как указано в параграфе 4.e документа IETF Trust Legal Provisions, без каких-либо гарантий (как указано в Simplified BSD License).

Оглавление

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

1. Введение

[RFC8345] определяет модели данных YANG [RFC6020] [RFC7950] абстрактной (базовой) сети и сетевой топологии, которые можно дополнить зависящими от технологии деталями для построения соответствующей технологии модели.

Этот документ определяет модель данных YANG для сетевых топологий канального уровня (L2) путем дополнения моделей данных базовой сети (параграф 6.1 в [RFC8345]) и базовой топологии (параграф 6.2 в [RFC8345]) относящимися к уровню L2 атрибутами. Пример представлен в приложении B.

Такая модель данных имеет много применений. Например, в контексте интерфейса в систему маршрутизации I2RS3 узлы сети могут использовать модель данных для фиксации своего представления о топологии сети в целом и раскрытия его контроллеру сети. Контроллер может использовать экземпляры данных топологии для сравнения и согласования со своим представлением о топологии сети. В дополнение к этому узлы сети могут сравнивать и согласовывать эти представления между собой самостоятельно или с помощью контроллера. Помимо элементов сети и самого контекста I2RS контроллер сети может применять модель данных для представления контролируемой им топологии внешним приложениям. Другие варианты применения модели данных рассмотрены в [I2RS-UR].

В документе применяются базовые типы YANG, определенные в [RFC6991], и принимается архитектура хранилища NMDA4 [RFC8342].

2. Терминология

Ключевые слова необходимо (MUST), недопустимо (MUST NOT), требуется (REQUIRED), нужно (SHALL), не следует (SHALL NOT), следует (SHOULD), не нужно (SHOULD NOT), рекомендуется (RECOMMENDED), не рекомендуется (NOT RECOMMENDED), возможно (MAY), необязательно (OPTIONAL) в данном документе интерпретируются в соответствии с BCP 14 [RFC2119] [RFC8174] тогда и только тогда, когда они выделены шрифтом, как показано здесь.

Термины для описания модулей YANG определены в [RFC7950], значения символов, используемых в диаграммах деревьев, — в [RFC8340].

3. Модель топологии L2

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

Связь модуля топологии L2 с модулями базовой сети и сетевой топологии показана на рисунке 1. Для представления топологии L2 модели базовой сети и сетевой топологии дополнены относящейся к L2 информацией, такой как идентификаторы, сущности (элементы, например, PBB5 [IEEE802.1ah], QinQ [IEEE802.1ad], VXLAN6 [RFC7348]), атрибуты и состояния сетей L2, узлы, каналы и точки завершения. Часть информации может быть собрана протоколом LLDP7 [IEEE802.1AB] или другим протоколом L2, а другая часть настроена в локальной конфигурации.

+---------------------+
|    ietf-network     |
+----------^----------+
           |
           |
+---------------------+
|ietf-network-topology|
+----------^----------+
           |
           |
+----------^----------+
|   ietf-l2-topology  |
+---------------------+

Рисунок 1. Структура модуля YANG.


Структура модуля YANG ietf-l2-topology в форме дерева представлена ниже.

   module: ietf-l2-topology
     augment /nw:networks/nw:network/nw:network-types:
       +--rw l2-topology!
     augment /nw:networks/nw:network:
       +--rw l2-topology-attributes
          +--rw name?    string
          +--rw flags*   l2-flag-type
     augment /nw:networks/nw:network/nw:node:
       +--rw l2-node-attributes
          +--rw name?                 string
          +--rw flags*                node-flag-type
          +--rw bridge-id*            string
          +--rw management-address*   inet:ip-address
          +--rw management-mac?       yang:mac-address
          +--rw management-vlan?      string
     augment /nw:networks/nw:network/nt:link:
       +--rw l2-link-attributes
          +--rw name?        string
          +--rw flags*       link-flag-type
          +--rw rate?        uint64
          +--rw delay?       uint32
          +--rw auto-nego?   boolean
          +--rw duplex?      duplex-mode
     augment /nw:networks/nw:network/nw:node/nt:termination-point:
       +--rw l2-termination-point-attributes
          +--rw interface-name?       string
          +--rw mac-address?          yang:mac-address
          +--rw port-number*          uint32
          +--rw unnumbered-id*        uint32
          +--rw encapsulation-type?   identityref
          +--rw outer-tag?            dot1q-types:vid-range-type {VLAN}?
          +--rw outer-tpid?           dot1q-types:dot1q-tag-type {QinQ}?
          +--rw inner-tag?            dot1q-types:vid-range-type {VLAN}?
          +--rw inner-tpid?           dot1q-types:dot1q-tag-type {QinQ}?
          +--rw lag?                  boolean
          +--rw member-link-tp*
                 -> /nw:networks/network/node/nt:termination-point/tp-id
          +--rw vxlan {VXLAN}?
             +--rw vni-id?   vni

     notifications:
       +---n l2-node-event
       |  +--ro event-type?           l2-network-event-type
       |  +--ro node-ref?
                         -> /nw:networks/network[nw:network-id=current()
                            /../network-ref]/node/node-id
       |  +--ro network-ref?          -> /nw:networks/network/network-id
       |  +--ro l2-topology!
       |  +--ro l2-node-attributes
       |     +--ro name?                 string
       |     +--ro flags*                node-flag-type
       |     +--ro bridge-id*            uint64
       |     +--ro management-address*   inet:ip-address
       |     +--ro management-mac?       yang:mac-address
       |     +--ro management-vlan?      string
       +---n l2-link-event
       |  +--ro event-type?           l2-network-event-type
       |  +--ro link-ref?
                         -> /nw:networks/network[nw:network-id=current()
                            /../network-ref]/nt:link/link-id
       |  +--ro network-ref?          -> /nw:networks/network/network-id
       |  +--ro l2-topology!
       |  +--ro l2-link-attributes
       |     +--ro name?        string
       |     +--ro flags*       link-flag-type
       |     +--ro rate?        uint64
       |     +--ro delay?       uint32
       |     +--ro auto-nego?   boolean
       |     +--ro duplex?      duplex-mode
       +---n l2-termination-point-event
          +--ro event-type?                        l2-network-event-type
          +--ro tp-ref?
                         -> /nw:networks/network[nw:network-id=current()
                            /../network-ref]/node[nw:node-id=current()
                            /../node-ref]/nt:termination-point/tp-id
          +--ro node-ref?
                         -> /nw:networks/network[nw:network-id=current()
                            /../network-ref]/node/node-id
          +--ro network-ref?          -> /nw:networks/network/network-id
          +--ro l2-topology!
          +--ro l2-termination-point-attributes
             +--ro interface-name?       string
             +--ro mac-address?          yang:mac-address
             +--ro port-number*          uint32
             +--ro unnumbered-id*        uint32
             +--ro encapsulation-type?   identityref
             +--ro outer-tag?         dot1q-types:vid-range-type {VLAN}?
             +--ro outer-tpid?        dot1q-types:dot1q-tag-type {QinQ}?
             +--ro inner-tag?         dot1q-types:vid-range-type {VLAN}?
             +--ro inner-tpid?        dot1q-types:dot1q-tag-type {QinQ}?
             +--ro lag?               boolean
             +--ro member-link-tp*
                 -> /nw:networks/network/node/nt:termination-point/tp-id
             +--ro vxlan {VXLAN}?
                +--ro vni-id?   vni

Модуль YANG для топологии L2 дополняет модули ietf-network и ietf-network-topology.

  • Вводится новый тип l2-network-type, представляемый контейнером и помещаемый под контейнером network-types модуля ietf-network, определенного в параграфе 6.1 [RFC8345].

  • Введены дополнительные атрибуты сети в группировке l2-network-attributes, дополняющие список network модуля ietf-network. Атрибуты включают имя сети L2 и набор флагов. Каждый тип флагов представлен отдельной сущностью (объектом).

  • Вводятся дополнительные элементы (объекты) данных для узлов L2 путем дополнения списка node базового модуля ietf-network. Новые объекты включают идентификатор узла L2, адрес управления, MAC-адрес управления, VLAN для управления и набор флагов.

  • Вводятся дополнительные элементы (объекты) данных для точек завершения L2 путем дополнения списка termination-point модуля ietf-network-topology, определенного в параграфе 6.2 [RFC8345]. Новые объекты включают имя интерфейса, тип инкапсуляции, индикацию поддержки агрегирования (lag) и атрибуты, связанные с типом точки завершения L2.

  • Каналы в модуле ietf-network-topology дополняются набором параметров L2, позволяющим связать канал с именем, набором атрибутов канала L2 и флагами.

  • В модуле введены необязательные атрибуты L2, связанные с технологией, как свойства L2, поскольку такие атрибуты могут быть полезны для раскрытия вышележащим службам (приложениям). Отметим, что изучение или настройка таких расширенных атрибутов L2 выходят за рамки модуля YANG для топологии L2 и следует использовать для них дополнительные модули YANG (например, [TRILL-YANG]).

4. Модуль YANG для топологии L2

Этот модуль использует типы, определенные в [RFC6991], [RFC7224], [IEEE802.1Qcp] и [RFC8345], а также ссылается на [IEEE802.1Q-2014], [IEEE802.1ad], [RFC7348] и [RFC7727].

   <CODE BEGINS> file "ietf-l2-topology@2020-11-15.yang"
   module ietf-l2-topology {
     yang-version 1.1;
     namespace "urn:ietf:params:xml:ns:yang:ietf-l2-topology";
     prefix l2t;

     import ietf-network {
       prefix nw;
       reference
         "RFC 8345: A YANG Data Model for Network Topologies";
     }
     import ietf-network-topology {
       prefix nt;
       reference
         "RFC 8345: A YANG Data Model for Network Topologies";
     }
     import ietf-inet-types {
       prefix inet;
       reference
         "RFC 6991:Common YANG Data Types";
     }
     import ietf-yang-types {
       prefix yang;
       reference
         "RFC 6991:Common YANG Data Types";
     }
     import iana-if-type {
       prefix ianaift;
       reference
         "RFC 7224: IANA Interface Type YANG Module";
     }
     import ieee802-dot1q-types {
       prefix dot1q-types;
       reference
         "IEEE Std 802.1Qcp-2018: Bridges and Bridged
          Networks - Amendment: YANG Data Model";
     }

     organization
       "IETF I2RS (Interface to the Routing System) Working Group";
     contact
       "WG Web:   <https://datatracker.ietf.org/wg/i2rs> 
        WG List:  <mailto:i2rs@ietf.org>

        Editor:    Jie Dong
                  <mailto:jie.dong@huawei.com> 

        Editor:    Xiugang Wei
                  <mailto:weixiugang@huawei.com> 

        Editor:    Qin Wu
                  <mailto:bill.wu@huawei.com> 

        Editor:    Mohamed Boucadair
                  <mailto:mohamed.boucadair@orange.com> 

        Editor:    Anders Liu
                  <mailto:andersliu@tencent.com>"; 
     description
       "Этот модуль определяет базовую модель сетевой топологии L2.

        Авторские права (Copyright (c) 2020) принадлежат IETF Trust 
        и лицам, указанным как авторы кода. Все права защищены.

        Распространение и использование в исходной или двоичной форме
        с изменениями или без таковых разрешено в соответствии с
        упрощенной лицензией BSD, как указано в параграфе 4.c
        документа IETF Trust Legal Provisions применительно к
        документам IETF (https://trustee.ietf.org/license-info).

        Данная версия модуля YANG является частью RFC 8944, где 
        правовые аспекты изложены более полно.";

     revision 2020-11-15 {
       description
         "Исходный выпуск.";
       reference
         "RFC 8944: A YANG Data Model for Layer 2 Network Topologies";
     }

     feature VLAN {
       description
         "Включает поддержку VLAN в соответствии с IEEE 802.1Q.";
       reference
         "IEEE Std 802.1Q-2014: Bridges and Bridged Networks";
     }

     feature QinQ {
       description
         "Включает поддержку двойных тегов QinQ IEEE 802.1ad.";
       reference
         "IEEE Std 802.1ad: Provider Bridges";
     }

     feature VXLAN {
       description
         "Включает поддержку VXLAN в соответствии с RFC 7348.";
       reference
         "RFC 7348: Virtual eXtensible Local Area Network (VXLAN):
                    A Framework for Overlaying Virtualized Layer 2
                    Networks over Layer 3 Networks";
     }

     identity flag-identity {
       description
         "базовый тип для флагов.";
     }

     identity eth-encapsulation-type {
       base ianaift:iana-interface-type;
       description
         "Базовое отождествление, из которого выводятся конкретные
          типы инкапсуляции Ethernet.";
       reference
         "RFC 7224: IANA Interface Type YANG Module";
     }

     identity ethernet {
       base eth-encapsulation-type;
       description
         "Естественная инкапсуляция Ethernet.";
     }

     identity vlan {
       base eth-encapsulation-type;
       description
         "Инкапсуляция VLAN.";
     }

     identity qinq {
       base eth-encapsulation-type;
       description
         "Инкапсуляция QinQ.";
     }

     identity pbb {
       base eth-encapsulation-type;
       description
         "Инкапсуляция PBB в соответствии с IEEE 802.1ah.";
     }

     identity trill {
       base eth-encapsulation-type;
       description
         "Инкапсуляция TRILL.";
     }

     identity vpls {
       base eth-encapsulation-type;
       description
         "Инкапсуляция интерфейса VPLS.";
     }

     identity vxlan {
       base eth-encapsulation-type;
       description
         "Инкапсуляция VXLAN MAC в UDP.";
       reference
         "RFC 7348: Virtual eXtensible Local Area  Network (VXLAN):
                    A Framework for Overlaying Virtualized Layer 2
                    Networks over Layer 3 Networks";
     }

     typedef vni {
       type uint32 {
         range "0..16777215";
       }
       description
         "Идентификатор сети или сегмента VXLAN, обеспечивающий
          сосуществование до 16M сегментов VXLAN в одном
          административном домене.

          Использование значения 0 зависит от реализации.";
       reference
         "RFC 7348: Virtual eXtensible Local Area  Network (VXLAN):
                    A Framework for Overlaying Virtualized Layer 2
                    Networks over Layer 3 Networks";
     }

     typedef l2-flag-type {
       type identityref {
         base flag-identity;
       }
       description
         "Базовый тип для флагов L2. Примером типа флага L2 
          является trill, представляющий топологию типа trill.";
     }

     typedef node-flag-type {
       type identityref {
         base flag-identity;
       }
       description
         "Атрибуты флага узла. Примером может служить 
          физический узел.";
     }

     typedef link-flag-type {
       type identityref {
         base flag-identity;
       }
       description
         "Атрибуты флага соединения. Одним из примеров служит
          псевдопровод.";
     }

     typedef l2-network-event-type {
       type enumeration {
         enum addition {
           value 0;
           description
             "Добавлен узел, канал или точка завершения L2.";
         }
         enum removal {
           value 1;
           description
             "Удален узел, канал или точка завершения L2.";
         }
         enum update {
           value 2;
           description
             "Изменен узел, канал или точка завершения L2.";
         }
       }
       description
         "Тип события сети L2 для уведомления.";
     }

     typedef duplex-mode {
       type enumeration {
         enum full-duplex {
           description
             "Указывает полнодуплексный режим.";
         }
         enum half-duplex {
           description
             "Указывает полудуплексный режим.";
         }
       }
       description
         "Указывает тип дуплексного режима.";
     }

     grouping l2-network-type {
       description
         "Указывает, что топология относится к типу L2.";
       container l2-topology {
         presence "Indicates L2 Network Topology.";
         description
           "Наличие контейнерного узла указывает сетевую
            топологию L2.";
       }
     }

     grouping l2-topology-attributes {
       description
         "Атрибуты области действия топологии L2.";
       container l2-topology-attributes {
         description
           "Содержит атрибуты топологии L2.";
         leaf name {
           type string;
           description
             "Имя топологии.";
         }
         leaf-list flags {
           type l2-flag-type;
           description
             "Флаги топологии.";
         }
       }
     }

     grouping l2-node-attributes {
       description
         "Атрибуты узла L2.";
       container l2-node-attributes {
         description
           "Содержит атрибуты узла L2 .";
         leaf name {
           type string;
           description
             "Имя узла.";
         }
         leaf-list flags {
           type node-flag-type;
           description
             "Флаги узла. Могут служить для указания 
              атрибутов флага узла.";
         }
         leaf-list bridge-id {
           type string {
             pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){7}';
           }
           description
             "Идентификатор моста в форме строки октетов 
              из 8 шестнадцатеричных чисел. Включает 4 бита
              приоритета, 12-битовый идентификатор MSTI-ID и
              базовый идентификатор моста. Идентификаторы
              могут присутствовать для каждого экземпляра STP.";
           reference
             "RFC 7727: Spanning Tree Protocol (STP) Application of
                        the Inter-Chassis Communication Protocol
                        (ICCP)";
         }
         leaf-list management-address {
           type inet:ip-address;
           description
             "IP-адрес для управления.";
         }
         leaf management-mac {
           type yang:mac-address;
           description
             "MAC-адрес для управления мостом. Это может быть
              Bridge Base VLAN ID (VID), MAC-адрес интерфейса и пр.";
         }
         leaf management-vlan {
           type string;
           description
             "VLAN, поддерживающая адрес управления. Тип и значение
              реального VLAN ID будут относится к этой VLAN.";
         }
       }
     }

     grouping l2-link-attributes {
       description
         "L2 link attributes.";
       container l2-link-attributes {
         description
           "Содержит атрибуты соединения L2.";
         leaf name {
           type string;
           description
             "Link name.";
         }
         leaf-list flags {
           type link-flag-type;
           description
             "Флаги соединения. Может применяться для
              флагов атрибута соединения.";
         }
         leaf rate {
           type uint64;
           units "Kbps";
           description
             "Скорость соединения. Задает требования к 
              пропускной способности для конкретного 
              соединения между источником и получателем.";
         }
         leaf delay {
           type uint32;
           units "microseconds";
           description
             "Односторонняя задержка в микросекундах.";
         }
         leaf auto-nego {
           type boolean;
           default "true";
           description
             "Значение true при поддержке автосогласования,
              false, если автосогласование не поддерживается.";
         }
         leaf duplex {
           type duplex-mode;
           description
             "Раскрывает режим дуплека - full-duplex или half-duplex.";
         }
       }
     }

     grouping l2-termination-point-attributes {
       description
         "Атрибуты точки завершения L2.";
       container l2-termination-point-attributes {
         description
           "Содержит атрибуты точки завершения L2 .";
         leaf interface-name {
           type string;
           description
             "Имя интерфейса, которое может (но не обязано) 
              соответствовать ссылке на интерфейс содержащего узла,
              т. е. имя пути к узлу данных соответствующего 
              интерфейса на содержащем узле напоминает тип
              данных interface-ref, определенный в RFC 8343.
              Следует отметить, что тип interface-ref из RFC 8343
              нельзя использовать напрямую, поскольку он служит для
              ссылки на интерфейс в хранилище данных одного узла сети,
              а не однозначного указания интерфейсов в сети.";
         }
         leaf mac-address {
           type yang:mac-address;
           description
             "MAC-адрес интерфейса для управления логическим каналом.";
         }
         leaf-list port-number {
           type uint32;
           description
             "Список номеров портов моста, для которых каждая запись
              содержит информацию управления.";
         }
         leaf-list unnumbered-id {
           type uint32;
           description
             "Список идентификаторов безадресных интерфейсов.
              Такой идентификатор будет соответствовать значению
              ifIndex для интерфейса, т. е. значению ifIndex для
              ifEntry, представляющей интерфейс в реализации, где
              поддерживается Interfaces Group MIB (RFC 2863).";
         }
         leaf encapsulation-type {
           type identityref {
             base eth-encapsulation-type;
           }
           description
             "Тип инкапсуляции для этой точки завершения.";
         }
         leaf outer-tag {
           if-feature "VLAN";
           type dot1q-types:vid-range-type;
           description
             "Внешний тег VLAN. Может указываться список VLAN
              или не перекрывающиеся диапазоны VLAN.";
         }
         leaf outer-tpid {
           if-feature "QinQ";
           type dot1q-types:dot1q-tag-type;
           description
             "Указывает конкретный тип 802.1Q внешнего тега VLAN.";
         }
         leaf inner-tag {
           if-feature "VLAN";
           type dot1q-types:vid-range-type;
           description
             "Внутренний тег VLAN. Может указываться список VLAN
              или не перекрывающиеся диапазоны VLAN";
         }
         leaf inner-tpid {
           if-feature "QinQ";
           type dot1q-types:dot1q-tag-type;
           description
             "Указывает конкретный тип 802.1Q внутреннего тега VLAN .";
         }
         leaf lag {
           type boolean;
           default "false";
           description
             "Указывает поддерживается ли агрегирование (lag).
              Значение true говорит о наличии поддержки.";
         }
         leaf-list member-link-tp {
           when "../lag = 'true'" {
             description
               "Используется только при поддержке интерфейсов lag.";
           }
           type leafref {
             path "/nw:networks/nw:network/nw:node"
                + "/nt:termination-point/nt:tp-id";
           }
           description
             "Список точек завершения каналов, связанных с конкретной
              точкой завершения L2.";
         }
         container vxlan {
           when "derived-from-or-self(../encapsulation-type, "
              + "'l2t:vxlan')" {
             description
               "Применимо лишь при инкапсуляции Ethernet типа vxlan.";
           }
           if-feature "VXLAN";
           leaf vni-id {
             type vni;
             description
               "VXLAN Network Identifier (VNI).";
           }
           description
             "Тип инкапсуляции VXLAN.";
         }
       }
     }

     augment "/nw:networks/nw:network/nw:network-types" {
       description
         "Вводит новый тип сети для топологии L2.";
       uses l2-network-type;
     }
     augment "/nw:networks/nw:network" {
       when '/nw:networks/nw:network/nw:network-types/l2t:l2-topology' {
         description
           "Параметры дополнения, применимые лишь к сети с топологией L2.";
       }
       description
         "Конфигурационные параметры для сети L2 в целом.";
       uses l2-topology-attributes;
     }
     augment "/nw:networks/nw:network/nw:node" {
       when '/nw:networks/nw:network/nw:network-types/l2t:l2-topology' {
         description
           "Параметры дополнения, применимые лишь к сети с топологией L2.";
       }
       description
         "Конфигурационные параметры для L2 на уровне узла.";
       uses l2-node-attributes;
     }
     augment "/nw:networks/nw:network/nt:link" {
       when '/nw:networks/nw:network/nw:network-types/l2t:l2-topology' {
         description
           "Параметры дополнения, применимые лишь к сети с топологией L2.";
       }
       description
         "Дополнение топологической информации канала L2.";
       uses l2-link-attributes;
     }
     augment "/nw:networks/nw:network/nw:node/nt:termination-point" {
       when '/nw:networks/nw:network/nw:network-types/l2t:l2-topology' {
         description
           "Параметры дополнения, применимые лишь к сети с топологией L2.";
       }
       description
         "Дополнение топологических данных точки завершения L2.";
       uses l2-termination-point-attributes;
     }

     notification l2-node-event {
       description
         "Уведомление о событии на узле L2.";
       leaf event-type {
         type l2-network-event-type;
         description
           "Тип события.";
       }
       uses nw:node-ref;
       uses l2-network-type;
       uses l2-node-attributes;
     }

     notification l2-link-event {
       description
         "Уведомление о событии на канале L2.";
       leaf event-type {
         type l2-network-event-type;
         description
           "Тип события.";
       }
       uses nt:link-ref;
       uses l2-network-type;
       uses l2-link-attributes;
     }

     notification l2-termination-point-event {
       description
         "Уведомление о событии в точке завершения L2.";
       leaf event-type {
         type l2-network-event-type;
         description
           "Тип события.";
       }
       uses nt:tp-ref;
       uses l2-network-type;
       uses l2-termination-point-attributes;
     }
   }
   <CODE ENDS>

5. Взаимодействие с IANA

Агентство IANA зарегистрировало приведенные ниже URI в субреестре ns реестра The IETF XML Registry [RFC3688]:

   URI:  urn:ietf:params:xml:ns:yang:ietf-l2-topology
   Registrant Contact:  The IESG.
   XML:  N/A; the requested URI is an XML namespace.

   URI:  urn:ietf:params:xml:ns:yang:ietf-l2-topology-state
   Registrant Contact:  The IESG.
   XML:  N/A; the requested URI is an XML namespace.

   IANA has registered the following YANG modules in the "YANG Module
   Names" subregistry [RFC6020] within the "YANG Parameters" registry.

   Name:  ietf-l2-topology
   Namespace:  urn:ietf:params:xml:ns:yang:ietf-l2-topology
   Prefix:  l2t
   Reference:  RFC 8944

   Name:  ietf-l2-topology-state
   Namespace:  urn:ietf:params:xml:ns:yang:ietf-l2-topology-state
   Prefix:  l2t-s
   Reference:  RFC 8944

Эти модули не поддерживаются IANA.

6. Вопросы безопасности

Заданные этим документом модули YANG определяют схему для данных, предназначенную для доступа через сеть с использованием протоколов управления, таких как NETCONF8 [RFC6241] или RESTCONF [RFC8040]. Нижним уровнем NETCONF служит защищенный транспорт с обязательной поддержкой SSH (Secure Shell) [RFC6242]. Нижним уровнем RESTCONF служит протокол HTTPS с обязательной поддержкой защиты на транспортном уровне (TLS) [RFC8446]. Модель доступа к конфигурации сети (NACM — Network Configuration Access Control Model) [RFC8341] обеспечивает возможность разрешить доступ лишь определенных пользователей NETCONF или RESTCONF к заранее заданному подмножеству операций NETCONF или RESTCONF и содержимого.

Модуль топологии L2 задает данные, которые могут быть настраиваемыми в некоторых экземплярах (например, для виртуальной топологии, создаваемой клиентским приложением). В таких случаях вредоносный клиент может создавать нежелательную топологию. В частности, он может пытаться удалить или добавить узлы, каналы или точки завершения, создавая или удаляя соответствующие элементы в списках узлов, каналов или точек завершения. При изучении топологии сервер будет автоматически запрещать такие попытки недопустимой настройки. Для настроенной топологии (из хранилища intended) нежелательная конфигурация может вступить в силу и попасть в хранилище рабочей конфигурации [RFC8342], приводя к нарушению работы служб, обеспечиваемых через такую топологию. Поэтому важно применять NACM для предотвращения изменения топологии не имеющими полномочий клиентами.

В этом модуле данных YANG определено множество узлов данных, которые разрешают запись, создание и удаление (т. е. По умолчанию config имеет значение true). Эти узлы могут быть конфиденциальными или уязвимыми в некоторых сетевых средах. Запись в такие узлы (например, edit-config) без должной защиты может негативно влиять на работу сети. Ниже перечислены субдеревья и узлы, которые могут быть конфиденциальны или уязвимы.

l2-network-attributes

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

l2-node-attributes

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

l2-link-attributes

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

l2-termination-point-attributes:

Враждебный клиент может пытаться сорвать настройку важных атрибутов точки завершения (например, maximum-frame-size).

Некоторые из доступных для чтения узлов в этом модуле YANG могут быть конфиденциальны или уязвимы в той или иной сетевой среде. Важно контролировать доступ к таким объектам (например, get, get-config, notification). В частности, модуль YANG для топологии L2 может раскрывать конфиденциальную информацию, например, MAC-адреса устройств или идентификаторы VLAN илиVXLAN. Неограниченный доступ к такой информации может приводить к нарушению конфиденциальности. Из MAC-адресов сетевых устройств можно получить информацию об их размещении для обхода защиты таких данных в операционной системе.

7. Литература

7.1. Нормативные документы

[RFC2119] Bradner, S., «Key words for use in RFCs to Indicate Requirement Levels», BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, <https://www.rfc-editor.org/info/rfc2119>.

[RFC3688] Mealling, M., «The IETF XML Registry», BCP 81, RFC 3688, DOI 10.17487/RFC3688, January 2004, <https://www.rfc-editor.org/info/rfc3688>.

[RFC6020] Bjorklund, M., Ed., «YANG — A Data Modeling Language for the Network Configuration Protocol (NETCONF)», RFC 6020, DOI 10.17487/RFC6020, October 2010, <https://www.rfc-editor.org/info/rfc6020>.

[RFC6241] Enns, R., Ed., Bjorklund, M., Ed., Schoenwaelder, J., Ed., and A. Bierman, Ed., «Network Configuration Protocol (NETCONF)», RFC 6241, DOI 10.17487/RFC6241, June 2011, <https://www.rfc-editor.org/info/rfc6241>.

[RFC6242] Wasserman, M., «Using the NETCONF Protocol over Secure Shell (SSH)», RFC 6242, DOI 10.17487/RFC6242, June 2011, <https://www.rfc-editor.org/info/rfc6242>.

[RFC6991] Schoenwaelder, J., Ed., «Common YANG Data Types», RFC 6991, DOI 10.17487/RFC6991, July 2013, <https://www.rfc-editor.org/info/rfc6991>.

[RFC7224] Bjorklund, M., «IANA Interface Type YANG Module», RFC 7224, DOI 10.17487/RFC7224, May 2014, <https://www.rfc-editor.org/info/rfc7224>.

[RFC7348] Mahalingam, M., Dutt, D., Duda, K., Agarwal, P., Kreeger, L., Sridhar, T., Bursell, M., and C. Wright, «Virtual eXtensible Local Area Network (VXLAN): A Framework for Overlaying Virtualized Layer 2 Networks over Layer 3 Networks», RFC 7348, DOI 10.17487/RFC7348, August 2014, <https://www.rfc-editor.org/info/rfc7348>.

[RFC7950] Bjorklund, M., Ed., «The YANG 1.1 Data Modeling Language», RFC 7950, DOI 10.17487/RFC7950, August 2016, <https://www.rfc-editor.org/info/rfc7950>.

[RFC8040] Bierman, A., Bjorklund, M., and K. Watsen, «RESTCONF Protocol», RFC 8040, DOI 10.17487/RFC8040, January 2017, <https://www.rfc-editor.org/info/rfc8040>.

[RFC8174] Leiba, B., «Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words», BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[RFC8341] Bierman, A. and M. Bjorklund, «Network Configuration Access Control Model», STD 91, RFC 8341, DOI 10.17487/RFC8341, March 2018, <https://www.rfc-editor.org/info/rfc8341>.

[RFC8345] Clemm, A., Medved, J., Varga, R., Bahadur, N., Ananthakrishnan, H., and X. Liu, «A YANG Data Model for Network Topologies», RFC 8345, DOI 10.17487/RFC8345, March 2018, <https://www.rfc-editor.org/info/rfc8345>.

[RFC8446] Rescorla, E., «The Transport Layer Security (TLS) Protocol Version 1.3», RFC 8446, DOI 10.17487/RFC8446, August 2018, <https://www.rfc-editor.org/info/rfc8446>.

7.2. Дополнительная литература

[I2RS-UR] Hares, S. and M. Chen, «Summary of I2RS Use Case Requirements», Work in Progress, Internet-Draft, draft-ietf-i2rs-usecase-reqs-summary-03, 15 November 2016, <https://tools.ietf.org/html/draft-ietf-i2rs-usecase-reqs-summary-03>.

[IEEE802.1AB] IEEE, «IEEE Standard for Local and metropolitan area networks — Station and Media Access Control Connectivity Discovery», IEEE Std 802.1AB-2016, DOI 10.1109/IEEESTD.2016.7433915, March 2016, <https://doi.org/10.1109/IEEESTD.2016.7433915>.

[IEEE802.1ad] IEEE, «IEEE Standard for Local and Metropolitan Area Networks—Virtual Bridged Local Area Networks—Amendment 4: Provider Bridges», IEEE Std 802.1ad-2005, DOI 10.1109/IEEESTD.2006.6044678, May 2006, <https://doi.org/10.1109/IEEESTD.2006.6044678>.

[IEEE802.1ah] IEEE, «IEEE Standard for Local and metropolitan area networks — Virtual Bridged Local Area Networks Amendment 7: Provider Backbone Bridges», IEEE Std 802.1ah-2008, DOI 10.1109/IEEESTD.2008.4602826, August 2008, <https://doi.org/10.1109/IEEESTD.2008.4602826>.

[IEEE802.1Q-2014] IEEE, «IEEE Standard for Local and metropolitan area networks—Bridges and Bridged Networks», IEEE 802.1Q-2014, DOI 10.1109/IEEESTD.2014.6991462, December 2014, <https://doi.org/10.1109/IEEESTD.2014.6991462>.

[IEEE802.1Qcp] IEEE, «IEEE Standard for Local and metropolitan area networks—Bridges and Bridged Networks—Amendment 30: YANG Data Model», IEEE Std 802.1Qcp-2018, DOI 10.1109/IEEESTD.2018.8467507, September 2018, <https://doi.org/10.1109/IEEESTD.2018.8467507>.

[RFC7727] Zhang, M., Wen, H., and J. Hu, «Spanning Tree Protocol (STP) Application of the Inter-Chassis Communication Protocol (ICCP)», RFC 7727, DOI 10.17487/RFC7727, January 2016, <https://www.rfc-editor.org/info/rfc7727>.

[RFC7951] Lhotka, L., «JSON Encoding of Data Modeled with YANG», RFC 7951, DOI 10.17487/RFC7951, August 2016, <https://www.rfc-editor.org/info/rfc7951>.

[RFC8340] Bjorklund, M. and L. Berger, Ed., «YANG Tree Diagrams», BCP 215, RFC 8340, DOI 10.17487/RFC8340, March 2018, <https://www.rfc-editor.org/info/rfc8340>.

[RFC8342] Bjorklund, M., Schoenwaelder, J., Shafer, P., Watsen, K., and R. Wilton, «Network Management Datastore Architecture (NMDA)», RFC 8342, DOI 10.17487/RFC8342, March 2018, <https://www.rfc-editor.org/info/rfc8342>.

[TRILL-YANG] Hao, W., Li, Y., Kumar, D., Durrani, M., Zhai, H., and L. Xia, «TRILL YANG Data Model», Work in Progress, Internet-Draft, draft-ietf-trill-yang-04, 20 December 2015, <https://tools.ietf.org/html/draft-ietf-trill-yang-04>.

Приложение A. Модуль для реализаций без поддержки NMDA

Определенный здесь модуль YANG ietf-l2-topology дополняет модули ietf-network и ietf-network-topology, разработанные для использования с реализациями, поддерживающими хранилища NMDA [RFC8342]. Для работы с реализациями, не поддерживающими NMDA, определен набор сопутствующих модулей, представляющих модель состояния сети и топологию, ietf-network-state и ietf-network-topology-state.

Для использования определенной в этом документе модели топологии L2 с реализациями без поддержки NMDA создан сопутствующий модуль, представляющий рабочее состояние топологии L2. Модуль ietf-l2-topology-state соответствует модулю ietf-l2-topology, определенному в разделе 4, однако он дополняет ietf-network-state и ietf-network-topology-state (вместо ietf-network и ietf-network-topology), а все его узлы данных являются ненастраиваемыми.

Модуль ietf-l2-topology не следует поддерживать в реализациях, поддерживающих NMDA. По этой причине модуль определен в информационном приложении.

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

   <CODE BEGINS> file "ietf-l2-topology-state@2020-11-15.yang"
   module ietf-l2-topology-state {
     yang-version 1.1;
     namespace "urn:ietf:params:xml:ns:yang:ietf-l2-topology-state";
     prefix l2t-s;

     import ietf-network-state {
       prefix nw-s;
       reference
         "RFC 8345: A YANG Data Model for Network Topologies";
     }
     import ietf-network-topology-state {
       prefix nt-s;
       reference
         "RFC 8345: A YANG Data Model for Network Topologies";
     }
     import ietf-l2-topology {
       prefix l2t;
       reference
         "RFC 8944: A YANG Data Model for Layer 2 Network Topologies";
     }

     organization
       "IETF I2RS (Interface to the Routing System) Working Group";
     contact
       "WG Web:   <http://tools.ietf.org/wg/i2rs/> 
        WG List:  <mailto:i2rs@ietf.org>

        Editor:    Jie Dong
                  <mailto:jie.dong@huawei.com> 
        Editor:    Xiugang Wei
                  <mailto:weixiugang@huawei.com> 
        Editor:    Qin Wu
                  <mailto:bill.wu@huawei.com> 
        Editor:    Mohamed Boucadair
                  <mailto:mohamed.boucadair@orange.com> 
        Editor:   Anders Liu
                  <andersliu@tencent.com>"; 
     description
       "Этот модуль определяет модель состояния сетевой топологии L2,
        представляя топологию, которая была изучена (learned) или
        является результатом применения модели ietf-l2-topolog,
        отражающей узлы данных этой модели.

        Модель отражает ietf-l2-topology, но включает только данные
        состояния, доступные лишь для чтения ( read-only). Модель не
        нужна при поддержке базовой инфраструктурой хранилища NMDA.

        Авторские права (Copyright (c) 2020) принадлежат IETF Trust 
        и лицам, указанным как авторы кода. Все права защищены.

        Распространение и использование в исходной или двоичной форме
        с изменениями или без таковых разрешено в соответствии с
        упрощенной лицензией BSD, как указано в параграфе 4.c
        документа IETF Trust Legal Provisions применительно к
        документам IETF (https://trustee.ietf.org/license-info).

        Данная версия модуля YANG является частью RFC 8944, где 
        правовые аспекты изложены более полно.";

     revision 2020-11-15 {
       description
         "Исходный выпуск.";
       reference
         "RFC 8944: A YANG Data Model for Layer 2 Network Topologies";
     }

     /*
      * Узлы данных
      */

     augment "/nw-s:networks/nw-s:network/nw-s:network-types" {
       description
         "Вводит новый тип сети для топологии L2.";
       uses l2t:l2-network-type;
     }

     augment "/nw-s:networks/nw-s:network" {
       when 'nw-s:network-types/l2t-s:l2-topology' {
         description
           "Параметры дополнения для сетей с топологией L2.";
       }
       description
         "Конфигурационные параметры сети L2 в целом.";
       uses l2t:l2-topology-attributes;
     }

     augment "/nw-s:networks/nw-s:network/nw-s:node" {
       when '../nw-s:network-types/l2t-s:l2-topology' {
         description
           "Параметры дополнения для сетей с топологией L2.";
       }
       description
         "Конфигурационные параметры L2 на уровне узла.";
       uses l2t:l2-node-attributes;
     }

     augment "/nw-s:networks/nw-s:network/nt-s:link" {
       when '../nw-s:network-types/l2t-s:l2-topology' {
         description
           "Параметры дополнения для сетей с топологией L2.";
       }
       description
         "Дополнение топологической информации канала L2.";
       uses l2t:l2-link-attributes;
     }

     augment "/nw-s:networks/nw-s:network/nw-s:node/"
           + "nt-s:termination-point" {
       when '../../nw-s:network-types/l2t-s:l2-topology' {
         description
           "Параметры дополнения для сетей с топологией L2.";
       }
       description
         "Дополнение данных топологии точки завершения L2.";
       uses l2t:l2-termination-point-attributes;
     }

     /*
      * Уведомления
      */

     notification l2-node-event {
       description
         "Уведомление о событии на узле L2.";
       leaf event-type {
         type l2t:l2-network-event-type;
         description
           "Event type.";
       }
       uses nw-s:node-ref;
       uses l2t:l2-network-type;
       uses l2t:l2-node-attributes;
     }

     notification l2-link-event {
       description
         "Уведомление о событии на канале L2.";
       leaf event-type {
         type l2t:l2-network-event-type;
         description
           "Event type.";
       }
       uses nt-s:link-ref;
       uses l2t:l2-network-type;
       uses l2t:l2-link-attributes;
     }

     notification l2-termination-point-event {
       description
         "Уведомление о событии в точке завершения L2.";
       leaf event-type {
         type l2t:l2-network-event-type;
         description
           "Event type.";
       }
       uses nt-s:tp-ref;
       uses l2t:l2-network-type;
       uses l2t:l2-termination-point-attributes;
     }
   }
   <CODE ENDS>

Приложение B. Пример

В этом приложении дан пример экземпляра дерева данных в представлении JSON [RFC7951]. Пример создает топологию ietf-l2-topology для показанной на рисунке 2 сети. Здесь имеется три узла — D1, D2, D3. В D1 имеется три точки завершения (1-0-1, 1-2-1, 1-3-1), в D2 тоже три (2-1-1, 2-0-1, 2-3-1), а в D3 — две (3-1-1 и 3-2-1). Точка завершения 1-0-1 поддерживает lag с двумя каналами 1-0-1-1 и 1-0-1-2. Имеется 6 односторонних каналов, по два между каждой парой точек.

            +------------+                   +------------+
            |     D1     |                   |     D2     |
   1-0-1-1 /-\          /-\                 /-\          /-\
<--------->| | 1-0-1    | |---------------->| | 2-1-1    | |
   1-0-1-2 | |    1-2-1 | |<----------------| |    2-0-1 | |
<--------> \-/  1-3-1   \-/                 \-/  2-3-1   \-/
            |   /----\   |                   |   /----\   |
            +---|    |---+                   +---|    |---+
                \----/                           \----/
                 A  |                             A  |
                 |  |                             |  |
                 |  |                             |  |
                 |  |       +------------+        |  |
                 |  |       |     D3     |        |  |
                 |  |      /-\          /-\       |  |
                 |  +----->| | 3-1-1    | |-------+  |
                 +---------| |    3-2-1 | |<---------+
                           \-/          \-/
                            |            |
                            +------------+

Рисунок 2. Пример топологии сети.


Соответствующий экземпляр дерева данных представлен ниже.

   {
     "ietf-network:networks": {
       "network": [
         {
           "network-id": "l2-topo-example",
           "node": [
             {
               "node-id": "D1",
               "ietf-network-topology:termination-point": [
                 {
                   "tp-id": "1-0-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:d0",
                     "lag": true,
                     "member-link-tp": [
                       "1-0-1-1",
                       "1-0-1-2"
                     ]
                   }
                 },
                 {
                   "tp-id": "1-0-1-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:d3"
                   }
                 },
                 {
                   "tp-id": "1-0-1-2",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:d4"
                   }
                 },
                 {
                   "tp-id": "1-2-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:d1"
                   }
                 },
                 {
                   "tp-id": "1-3-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:d2"
                   }
                 }
               ],
               "ietf-l2-topology:l2-node-attributes": {
                 "management-address": [
                   "192.0.2.1",
                   "2001:db8:0:1::"
                 ]
               }
             },
             {
               "node-id": "D2",
               "ietf-network-topology:termination-point": [
                 {
                   "tp-id": "2-0-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:e0"
                   }
                 },
                 {
                   "tp-id": "2-1-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:e1"
                   }
                 },
                 {
                   "tp-id": "2-3-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:e2"
                   }
                 }
               ],
               "ietf-l2-topology:l2-node-attributes": {
                 "management-address": [
                   "192.0.2.2",
                   "2001:db8:0:2::"
                 ]
               }
             },
             {
               "node-id": "D3",
               "ietf-network-topology:termination-point": [
                 {
                   "tp-id": "3-1-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:f0"
                   }
                 },
                 {
                   "tp-id": "3-2-1",
                   "ietf-l2-topology:l2-termination-point-attributes": {
                     "mac-address": "00:00:5e:00:53:f1"
                   }
                 }
               ],
               "ietf-l2-topology:l2-node-attributes": {
                 "management-address": [
                   "192.0.2.3",
                   "2001:db8:0:3::"
                 ]
               }
             }
           ],
           "ietf-network-topology:link": [
             {
               "link-id": "D1,1-2-1,D2,2-1-1",
               "source": {
                 "source-node": "D1",
                 "source-tp": "1-2-1"
               },
               "destination": {
                 "dest-node": "D2",
                 "dest-tp": "2-1-1"
               },
               "ietf-l2-topology:l2-link-attributes": {
                 "rate": "1000"
               }
             },
             {
               "link-id": "D2,2-1-1,D1,1-2-1",
               "source": {
                 "source-node": "D2",
                 "source-tp": "2-1-1"
               },
               "destination": {
                 "dest-node": "D1",
                 "dest-tp": "1-2-1"
               },
               "ietf-l2-topology:l2-link-attributes": {
                 "rate": "1000"
               }
             },
             {
               "link-id": "D1,1-3-1,D3,3-1-1",
               "source": {
                 "source-node": "D1",
                 "source-tp": "1-3-1"
               },
               "destination": {
                 "dest-node": "D3",
                 "dest-tp": "3-1-1"
               },
               "ietf-l2-topology:l2-link-attributes": {
                 "rate": "1000"
               }
             },
             {
               "link-id": "D3,3-1-1,D1,1-3-1",
               "source": {
                 "source-node": "D3",
                 "source-tp": "3-1-1"
               },
               "destination": {
                 "dest-node": "D1",
                 "dest-tp": "1-3-1"
               },
               "ietf-l2-topology:l2-link-attributes": {
                 "rate": "1000"
               }
             },
             {
               "link-id": "D2,2-3-1,D3,3-2-1",
               "source": {
                 "source-node": "D2",
                 "source-tp": "2-3-1"
               },
               "destination": {
                 "dest-node": "D3",
                 "dest-tp": "3-2-1"
               },
               "ietf-l2-topology:l2-link-attributes": {
                 "rate": "1000"
               }
             },
             {
               "link-id": "D3,3-2-1,D2,2-3-1",
               "source": {
                 "source-node": "D3",
                 "source-tp": "3-2-1"
               },
               "destination": {
                 "dest-node": "D2",
                 "dest-tp": "2-3-1"
               },
               "ietf-l2-topology:l2-link-attributes": {
                 "rate": "1000"
               }
             }
           ]
         }
       ]
     }
   }

Благодарности

Авторы признательны за комментарии и предложения Susan Hares, Alia Atlas, Juergen Schoenwaelder, Mach Chen, Alexander Clemm, Sriganesh Kini, Oscar Gonzalez de Dios, Stig Venaas, Christian Huitema, Meral Shirazipour, Benjamin Kaduk, Don Fedyk.

Большое спасибо Ladislav Lhotka за рецензирование.

Адреса авторов

Jie Dong

Huawei

Huawei Campus

No. 156 Beiqing Rd.

Beijing

100095

China

Email: jie.dong@huawei.com

Xiugang Wei

Huawei

Huawei Campus

No. 156 Beiqing Rd.

Beijing

100095

China

Email: weixiugang@huawei.com

Qin Wu

Huawei

101 Software Avenue

Yuhua District

Nanjing

210012

China

Email: bill.wu@huawei.com

Mohamed Boucadair

Orange

Rennes 35000

France

Email: mohamed.boucadair@orange.com

Anders Liu

Tecent

Yinke Building

38 Haidian St

Haidian District

Beijing

100080

China

Email: andersliu@tencent.com

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

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

nmalykh@protocols.ru

1Internet Engineering Task Force.

2Internet Engineering Steering Group.

3Interface to the Routing System.

4Network Management Datastore Architecture — архитектура хранилища данных управления сетью.

5Provider Backbone Bridging — магистральные мосты провайдера.

6Virtual eXtensible Local Area Network — виртуальная расширяемая ЛВС.

7Link Layer Discovery Protocol — протокол обнаружения на канальном уровне.

8Network Configuration Protocol — протокол настройки сети.

Рубрика: RFC | Комментарии к записи RFC 8944 A YANG Data Model for Layer 2 Network Topologies отключены

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

image_print

P416 Portable Switch Architecture (PSA)

(working draft)

The P4.org Architecture Working Group

2020-10-12

PDF

Аннотация

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

Оглавление

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

1. Модель целевой архитектуры

PSA для языка P416 является аналогом стандартной библиотеки для языка программирования C. PSA определяет библиотеку типов, внешние элементы P416 (extern) для часто используемых функций (таких как счетчики, измерители и регистры), а также набор «путей пакетов» (packet path), позволяющие создавать программы P4 для управления потоками пакетов в коммутаторе с множеством портов (например, несколькими десятками портов Ethernet). Приведенные здесь API и рекомендации позволяют разработчикам создавать программы P4, переносимые между разными устройствами, соответствующими PSA.

Хотя некоторые части PSA специфичны для коммутаторов и архитектура Portable NIC Architecture (если такая будет разработана) будет существенно отличаться от PSA в этих частях, предполагается, что определенные здесь внешние элементы можно будет применять в разной архитектуре P416.

Модель PSA включает 6 программируемых блоков P4 и 2 фиксированных функциональных блока, как показано на рисунке 1. Поведение программируемых блоков задается на языке P4. Блоки PRE2 и BQE3 зависят от платформы и могут настраиваться на выполнение фиксированного набора операций.

 
Рисунок 1. Конвейер коммутатора PSA.

Входящие пакеты анализируются и проверяются на пригодность, а затем передаются во входной конвейер СД (сопоставление-действие, match-action), принимающий решение о дальнейшем пути пакетов. Входной сборщик (deparser) P4 задает содержимое пакета, отправляемое в буфер, и сопровождающие пакет метаданные. После входного конвейера пакет может быть реплицирован (т. е. созданы копии для нескольких выходных портов), а затем сохранен в буфере.

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

Разработчик программы для PSA должен определить объекты в программируемых блоках P4, которые соответствуют определенным ниже API (5. Программируемые блоки). Для входных и выходных данных программируемых блоков применяются шаблоны заданных в программе заголовков и метаданных. После определения 6 программируемых блоков программа P4 для архитектуры PSA создает основной объект (пакет, package) с программируемыми блоками в качестве аргументов (см. например, 7.3. Блок репликации пакетов).

Для повышения уровня переносимости программы P4 следует выполнять приведенные ниже рекомендации:

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

В этом документе приведены фрагменты нескольких программ P416, использующих включаемый файл psa.p4 и демонстрирующих возможности PSA. Исходный код этих программ доступен в репозитории, содержащем официальную спецификацию.

2. Соглашения об именах

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

  • Имена типов используют «стиль верблюда» (CamelCase) и суффикс _t. Например, PortId_t.
  • Типы элементов управления (control) и внешних объектов (extern) именуются в стиле CamelCase. Например, IngressParser.
  • Структурные типы именуются с использованием символов нижнего регистра, разделителей _ и суффикса _t. Например, psa_ingress_input_metadata_t.
  • Имена действий, внешних методов и функций, заголовков, структур и экземпляров элементов управления начинаются с символа нижнего регистра и используют разделители слов _. Например, send_to_port.
  • Перечисляемые элементы, определения констант и константы #define именуются с использованием символов верхнего регистра и разделителей _. Например, PSA_PORT_CPU.

Для зависимых от архитектуры метаданных (например, структур) используется префикс psa_.

3. Пути пакетов

На рисунке 2 показаны все возможные пути для пакетов, которые должна поддерживать реализация PSA. Кроме того, реализация может поддерживать дополнительные пути, не описанные здесь.

 
Рисунок 2. Пути пакетов в PSA.

В таблице 1 описаны сокращения, применяемые на рисунке 2. Между источником и получателем пакета может размещаться один или несколько аппаратных, программных или архитектурных компонентов PSA. Например, обычный групповой пакет проходит через блок репликации обычно также буфер пакетов) между выходом из входного сборщика и входом в выходной анализатор. В таблице указаны также программируемые компоненты архитектуры, служащие источниками и получателями на пути пакетов.

Таблица 1. Именование путей пакетов в коммутаторе.

Обозначение

Описание

Источник

Получатель

NFP

Обычный пакет из порта

Порт

Входной анализатор

NFCPU

Пакет из порта CPU

Порт CPU

Входной анализатор

NU

Обычный индивидуальный пакет из входного конвейера в выходной

Входной сборщик

Выходной анализатор

NM

Обычный реплицированный групповой пакет из входного конвейера в выходной

Выходной сборщик с помощью PRE

Входной анализатор (возможно несколько копий)

NTP

Обычный пакет в порт

Выходной сборщик

Порт

NTCPU

Обычный пакет в порт CPU

Выходной сборщик

Порт CPU

RESUB

Повторно представленный пакет

Входной сборщик

Входной анализатор

CI2E

Клон пакета из входного конвейера в выходной

Входной сборщик

Выходной анализатор

RECIRC

Рециркулированный пакет

Выходной сборщик

Входной анализатор

CE2E

Клон из выходного конвейера в него же

Выходной сборщик

Выходной анализатор

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

Таблица 2. Результаты однократной обработки пакетов во входном и выходном конвейере.

Обозначение

Описание

Дальнейшая обработка

Результирующие пакеты

NFP

Обычный пакет из порта

Входной конвейер

Не более 1 пакета CI2E, плюс не более 1 пакета RESUB, NU или NM. См. параграф 6.2. Поведение пакетов по завершении входной обработки.

NFCPU

Пакет из порта CPU

RESUB

Повторно представленный пакет

RECIRC

Рециркулированный пакет

NU

Обычный индивидуальный пакет из входного конвейера в выходной

Выходной конвейер

Не более 1 пакета CE2E, плюс не более 1 пакета RECIRC, NTP или NTCPU. См. параграф 6.5. Поведение пакетов по завершении выходной обработки.

NM

Обычный реплицированный групповой пакет из входного конвейера в выходной

CI2E

Клон пакета из входного конвейера в выходной

CE2E

Клон из выходного конвейера в него же

NTP

Обычный пакет в порт

Устройство на другой стороне

Определяется другим устройством

NTCPU

Обычный пакет в порт CPU

CPU

Определяется CPU

В PSA имеются поля метаданных, позволяющие программам P4 указать путь, по которому проходит каждый пакет, и следующий элемент управления для каждого пакета (см. раздел 6. Описание путей пакетов).

Для выходных пакетов выбор одного из выходных портов, порта CPU или порта рециркуляции выполняется предшествующим непосредственно этапом обработки (входной конвейер для NU, NM и CI2E, выходной конвейер для CE2E). При выходной обработке может быть принято решение об отбрасывании пакета вместо его передачи в выбранный ранее порт, но невозможно изменить выходной порт. Выбор выходного порта (или портов) обычно происходит во входном конвейере программы P4. Единственным исключением является выбор выходного порта для клонов CE2E, создаваемых непосредственно предшествующим этапом обработки. Причины этого исключения рассмотрены в приложении D.2. Неизменность выходного порта в процессе выходной обработки.

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

  • Исходный пакет принимается как NFP через порт 2. Входная обработка создает клон CI2E, адресованный в порт CPU (копия 1), и групповой пакет NM для multicast-группы 18, которая настроена в PacketReplicationEngine на создание копий для порта 5 (копия 2) и порта рециркуляции PSA_PORT_RECIRCULATE (копия 3).
  • Для копии 1 выполняется выходная обработка с передачей пакета по пути NTCPU в порт CPU.
  • Для копии 2 выполняется выходная обработка, которая создает клон CE2E для порта 8 (копия 4) и передает пакет NTP в порт 5.
  • Для копии 3 выполняется выходная обработка, которая возвращает RECIRC обратно на вход (копия 5).
  • Для копии 4 выполняется выходная обработка, передающая пакет NTP в порт 8.
  • Для копии 5 выполняется входная обработка, передающая пакет NU, направленный в порт 1 (копия 6).
  • Для копии 6 выполняется выходная обработка, которая отбрасывает пакет вместо передачи в порт 1.

Представленные выше действия являются просто примером, который может быть реализован программой P4. Не обязательно использовать все доступные пути пакетов. Нумерация копий выше приведена лишь для того, чтобы их различать в примере. Порты в примере также указаны произвольно. Реализация PSA может выполнять указанные выше шаги в разном порядке.

В PSA нет обязательного механизма для предотвращения создания из одного полученного пакета пакетов, порождающих бесконечную бесконечную рециркуляцию, повторное представление или клонирование CE2E. Такое поведение можно предотвратить тестированием программ P4 и/или включением в программу P4 метаданных «времени жизни», передаваемых с копиями пакетов подобно полю TTL в заголовках IPv4.

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

4. Типы данных PSA

4.1. Определения типов PSA

Каждая реализация PSA имеет конкретный размер в битах для описанных ниже типов в плоскости данных. Значения размера определяются во включаемом файле psa.p4 для конкретной платформы. Предполагается, что в разных реализациях PSA могут применяться разные размеры4.

Для каждого из этих типов интерфейс P4 Runtime API5 может использовать независимые от платформы размеры, которые определяются спецификацией P4 Runtime API, однако предполагается, что эти размеры будут не меньше соответствующих типов InHeader_t, перечисленных ниже, чтобы их можно было применять с любой платформой. Все реализации PSA должны применять в плоскости данных размеры типов, не превышающие соответствующий размер определенных типов InHeader_t.

/* В определениях применяется typedef, а не type, поэтому они просто
* задают другие имена для типа bit<W> с конкретным значением W. 
* В отличие от приведенных ниже определений type, значения, объявленные
* с именами типов typedef можно свободно перемешивать в выражениях как
* и другие щначения, объявленные с типом bit<W>. Приведенные ниже
* имена с type нельзя свободно перемешивать, если они не были сначала
* приведены к соответствующему типу typedef. Хотя это может оказаться
* неудобным при работе с арифметическими выражениями, такой подход 
* позволяет отметить все значения типов type в создаваемом автоматически
* API плоскости управления.
*
* Отметим, что размер typedef <name>Uint_t всегда совпадает с размером
* type <name>_t. */
typedef bit<unspecified> PortIdUint_t;
typedef bit<unspecified> MulticastGroupUint_t;
typedef bit<unspecified> CloneSessionIdUint_t;
typedef bit<unspecified> ClassOfServiceUint_t;
typedef bit<unspecified> PacketLengthUint_t;
typedef bit<unspecified> EgressInstanceUint_t;
typedef bit<unspecified> TimestampUint_t;
@p4runtime_translation("p4.org/psa/v1/PortId_t", 32)
type PortIdUint_t	PortId_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroup_t", 32)
type MulticastGroupUint_t 	MulticastGroup_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionId_t", 16)
type CloneSessionIdUint_t 	CloneSessionId_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfService_t", 8)
type ClassOfServiceUint_t 	ClassOfService_t;
@p4runtime_translation("p4.org/psa/v1/PacketLength_t", 16)
type PacketLengthUint_t	PacketLength_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstance_t", 16)
type EgressInstanceUint_t 	EgressInstance_t;
@p4runtime_translation("p4.org/psa/v1/Timestamp_t", 64)
type TimestampUint_t	Timestamp_t;
typedef error	ParserError_t;

const PortId_t PSA_PORT_RECIRCULATE = (PortId_t) unspecified;
const PortId_t PSA_PORT_CPU = (PortId_t) unspecified;
const CloneSessionId_t PSA_CLONE_SESSION_TO_CPU = (CloneSessiontId_t) unspecified;
/* Примечание. Все типы с InHeader в именах предназначены лишь для значений
* соответствующих типов в заголовках пакетов между устройством PSA и сервером
* P4Runtime, который управляет им.
*
* Указанные здесь размеры должны быть не меньше, чем будет применять 
* для этого типа любое из устройств PSA. Таким образом эти типы могут
* быть полезны для определения пакетов, передаваемых устройством PSA
* напрямую другим устройствам без прохождения через сервер P4Runtime
* (например, при отправке пакетов контроллеру или системе сбора данных
* на скоростях, превышающих возможности сервера P4Runtime). В таких
* случаях не требуется автоматического преобразования этих типов
* плоскостью данных PSA как при передаче серверу P4Runtime. Все такие
* преобразования следует включать в программу P4.
*
* Все размеры должны быть кратны 8, чтобы любое подмножество этих полей
* можно было использовать в одном определении заголовка P4 даже в случаях, 
* когда реализации P4 требуют от содержащего поля заголовка кратный 8
* битам размер. */
/* Причины использования typedef описаны выше. */
typedef bit<32> 	PortIdInHeaderUint_t;
typedef bit<32> 	MulticastGroupInHeaderUint_t;
typedef bit<16> 	CloneSessionIdInHeaderUint_t;
typedef bit<8> 	ClassOfServiceInHeaderUint_t;
typedef bit<16> 	PacketLengthInHeaderUint_t;
typedef bit<16> 	EgressInstanceInHeaderUint_t;
typedef bit<64> 	TimestampInHeaderUint_t;
@p4runtime_translation("p4.org/psa/v1/PortIdInHeader_t", 32)
type PortIdInHeaderUint_t		PortIdInHeader_t;
@p4runtime_translation("p4.org/psa/v1/MulticastGroupInHeader_t", 32)
type MulticastGroupInHeaderUint_t 	MulticastGroupInHeader_t;
@p4runtime_translation("p4.org/psa/v1/CloneSessionIdInHeader_t", 16)
type CloneSessionIdInHeaderUint_t 	CloneSessionIdInHeader_t;
@p4runtime_translation("p4.org/psa/v1/ClassOfServiceInHeader_t", 8)
type ClassOfServiceInHeaderUint_t 	ClassOfServiceInHeader_t;
@p4runtime_translation("p4.org/psa/v1/PacketLengthInHeader_t", 16)
type PacketLengthInHeaderUint_t	PacketLengthInHeader_t;
@p4runtime_translation("p4.org/psa/v1/EgressInstanceInHeader_t", 16)
type EgressInstanceInHeaderUint_t 	EgressInstanceInHeader_t;
@p4runtime_translation("p4.org/psa/v1/TimestampInHeader_t", 64)
type TimestampInHeaderUint_t		TimestampInHeader_t;

4.2. Поддерживаемые PSA типы метаданных

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

struct psa_ingress_parser_input_metadata_t {
	PortId_t		ingress_port;
	PSA_PacketPath_t	packet_path;
}

struct psa_egress_parser_input_metadata_t {
	PortId_t		egress_port;
	PSA_PacketPath_t	packet_path;
}

struct psa_ingress_input_metadata_t {
	// Все перечисленные значения инициализируются архитектурой до
	// начала выполнения блока управления Ingress.
	PortId_t		ingress_port;
	PSA_PacketPath_t	packet_path;
	Timestamp_t		ingress_timestamp;
	ParserError_t		parser_error;
}

struct psa_ingress_output_metadata_t {
	// В комментариях после полей указаны исходные значения на момент
	// начала выполнения блока управления Ingress.
	ClassOfService_t	class_of_service; 	// 0
	bool			clone;			// false
	CloneSessionId_t	clone_session_id;	// не определено
	bool			drop;			// true
	bool			resubmit;		// false
	MulticastGroup_t	multicast_group; 	// 0
	PortId_t		egress_port;		// не определено

struct psa_egress_input_metadata_t {
	ClassOfService_t	class_of_service;
	PortId_t		egress_port;
	PSA_PacketPath_t	packet_path;
	EgressInstance_t	instance;	/// экземпляр от PacketReplicationEngine
	Timestamp_t		egress_timestamp;
	ParserError_t		parser_error;
}

/// Эта структура является входным (in) параметром выходного сборщика. 
/// Она включает достаточно данных, чтобы сборщик мог решить вопрос о
/// рециркуляции пакета.
struct psa_egress_deparser_input_metadata_t {
	PortId_t	egress_port;
}

struct psa_egress_output_metadata_t {
	// В комментариях после полей указаны исходные значения на момент
	// начала выполнения блока управления Egress.
	bool			clone;			// false
	CloneSessionId_t	clone_session_id; 	// не определено
	bool			drop;			// false
}

4.3. Типы сопоставления

PSA поддерживает дополнительные типы match_kind сверх 3, определенных в спецификации P416.

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

Тип selector поддерживается только для таблиц в реализацией селектора действий (7.12. Селекторы действий).

4.3.1. Таблицы range

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

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

В таблицах range могут присутствовать поля lpm. В таких случаях для выбора записи применяется длина префикса, но при наличии нескольких совпадающих записей размер префикса не определяет их относительный приоритет и применяются лишь значения приоритета, заданные плоскостью управления. Если в таблице range имеются записи, заданные с помощью свойства записи const, относительный приоритет записей определяется по порядку их размещения в программе P4 (первая запись имеет наибольший приоритет).

4.3.2. Таблицы ternary

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

4.3.3. Таблицы lpm

Если в таблице нет полей range и ternary, но имеется поле lpm, такое поле должно быть единственным. В дополнение к нему могут присутствовать поля типа exact. Хотя одному ключу может соответствовать несколько записей таблицы, не может быть больше 1 записи, соответствующей каждому возможному размеру префикса в поле lpm, поскольку две присутствующие одновременно записи не могут иметь одинаковый ключ поиска. Всегда выбирается запись с максимальным размером совпадающего префикса. Плоскость управления не может задавать приоритет при создании записей в таких таблицах, поскольку приоритет всегда определяется размером префикса.

Если в таблице типа lpm имеются записи, определенные с помощью свойства const, их относительный приоритет определяется длиной префикса, а не порядком размещения в программе P4.

4.3.4. Таблицы exact

Если таблица включает лишь поля типа exact, любому ключу поиска будет соответствовать не более 1 записи, поскольку дубликаты ключей поиска не дозволены в таблице. В результате поле приоритета становится ненужным для определения соответствующей ключу записи. При наличии в таблице exact записей, определенных с помощью свойства const, ключу поиска не может соответствовать более одной записи, поэтому относительный приоритет записей в соответствии с их размещением в программе P4 также не имеет значения.

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

Реализации плоскости данных PSA, поддерживающие P4 Runtime API6, включают программу P4 Runtime Server, которая позволяет программировать устройство PSA в процессе работы с помощью одного или множества P4 Runtime Client. Для краткости P4 Runtime Server будет называть агентом, а P4 Runtime Client — контроллером. Контроллер может управлять множеством устройств с разными реализациями PSA.

Как отмечено в параграфе 4.1. Определения типов PSA, предполагается, что разные реализации PSA могут задавать размеры типов данных, которые напрямую связаны с объектами плоскости данных, например, портами, идентификаторами multicast-групп и т. п..

Предполагается, что некоторые реализации PSA будут использовать заметно меньше ресурсов для таких объектов как ключи таблиц и параметры действий, если плоскость данных сохраняет лишь небольшое число битов, требуемое для значений каждого типа. Например, реализация может определять PortId_t как bit<6> вместо bit<16> и сберечь за счет этого 10 Мбит хранилища при миллионе записей в таблице7.

P4 Runtime API использует величины, битовый размер которых не зависит от целевой платформы, для типов, указанных в параграфе 4.1. Определения типов PSA, с целью упрощения работы с этими типами в программах агента и контроллера. Для операций плоскости управления с таблицами поиска все операции отсечки или дополнения полей выполняются агентом (обычно отсечка выполняется при передаче от контроллера к устройству, а дополнение — при передаче от устройства в контроллер).

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

  • Операции плоскости управления над таблицами, где значения этих типов могут включаться как параметры действий или ключи.
  • Операции плоскости управления по анализу наборов значений где эти типы могут быть частью ключа.
  • Пакеты, передаваемые CPU (входные с точки зрения контроллера) или получаемые от него (выходные).
  • Поля в уведомлениях внешнего блока Digest (7.14. Дайджест пакета).
  • Поля данных в массиве Register (7.9. Регистры).

Отметим, что приведенный список не является исчерпывающим.

Для пакетов между плоскостью управления и устройством PSA существует проблема, связанная с тем, что многие реализации PSA могут ограничивать в программах P4 размеры полей заголовков кратными 8 битам значениями. Чтобы соблюсти это ограничение и позволить определение типов заголовков P4 с полями специфичных для PSA типов, совместимыми с разными реализациями PSA, были определены дополнительные типы, содержащие в именах InHeader. Например, PortIdInHeader_t подобен PortId_t, но должен иметь размер, кратный 8 битам и не меньше размера PortId_t.

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

Значения типа PortId_t имеют в реализациях PSA необычное свойство. Поскольку это может упростить некоторые аппаратные реализации, численные значения полей типа PortId_t в плоскости данных P4 могут не быть простыми диапазонами (например, 0 — 31, как можно было бы задать в программе плоскости управления для 32-портового устройства). Предполагается, что агент будет преобразовывать численные идентификаторы портов в контроллере идентификаторы портов плоскости данных и обратно для каждого из описанных выше каналов взаимодействия между контроллером и плоскостью данных. Файл psa.p4 содержит аннотацию p4runtime_translation для определений типов PortId_t и PortIdInHeader_t. Это позволяет компилятору отметить все случаи применения значений этих типов, доступные из P4Runtime API, чтобы программы агента знали о необходимости преобразования идентификаторов. Это позволяет не указывать специально все случаи использования этих типов в программе P4.

Такой подход требует явного приведения типов к bit<W> для выполнения арифметических операций. Включаемый файл psa.p4 определяет PortIdUint_t как typedef с таким же размером, как type PortId_t, что позволяет привести значения типа PortId_t к типу PortIdUint_t, а затем выполнить с ними арифметические операции P4. Результат нужно явно привести обратно к типу PortId_t, если его нужно передать в поле метаданных этого типа. Соответствующие типы с Uint в именах определены для всех типов PSA. Из-за этого преобразования рекомендуется трактовать значения типа PortId_t как значения перечисляемого типа (enum). Сравнение двух значений этого типа на равенство или неравенство допустимо, также как присваивание значений другим переменным или параметрам того же типа, но почти все прочие операции ведут к ошибкам. При сопоставлении значения типа PortId_t как части ключа таблицы, нужно всегда проверять точное или шаблонное совпадение для каждого бита значения (т. е. сопоставление ternary с шаблоном для всех битов или lpm с префиксом нулевого размера). При попытке выполнить любое из указанных ниже действий со значением типа PortId_t или PortIdInHeader_t численное преобразование приведет к ошибкам в программе.

  • Сопоставление ключа с подмножеством битов или диапазоном.
  • Сопоставление номера порта с использованием оператора сравнения < или >.
  • Сравнение номера порта с конкретными литеральными значениями (например, 0 или 0xff). Вместо этого рекомендуется сравнивать такие значения, используя их как поля ключа поиска в таблице или поля ключа набора значений анализатора, со значениями, установленными плоскостью управления (которые транслируются в соответствующие значения для устройства программой агента плоскости управления). Разумно также сравнивать значения портов с символьными константами PSA_PORT_CPU или PSA_PORT_RECIRCULATE, которые имеют конкретные числовые значения для платформы.
  • Выполнение арифметических операций для значения с намерением получить значения, соответствующее другому порту устройства. Некоторые числовые значения могут не соответствовать ни одному из портов устройства и номера портов не обязаны быть последовательными.

Приведенный выше список не является исчерпывающим.

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

  • PortId_t или PortIdInHeader_t;
  • ClassOfService_t или ClassOfServiceInHeader_t;

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

  • MulticastGroup_t — 0 является особым значением, указывающим отсутствие групповой репликации для пакета, поэтому данный тип является исключением из указанного выше правила поддержки плоскостью управления значения 0.

  • CloneSessionId_t.

  • PacketLength_t.

  • EgressInstance_t.

  • Timestamp_t9

Отметим, что для всех этих типов имеются аннотации p4runtime_translation во включаемом файле psa.p4, чтобы при генерации компилятором файла P4Runtime P4Info из исходной программы в этот файл были включены типы, указанные p4runtime_translation, вместо зависимых от платформы размеров типа. Для одной программы P4 содержимое P4Info должно совпадать для всех платформ.

Если размер типа, указанный в качестве второго параметра p4runtime_translation, отличается от размера для платформы (или базового типа), предполагается, что сервер P4Runtime выполнит подходящее приведение типа. Кроме того, в процессе работы могут быть включены более сложные численные преобразования для любого типа, аннотированного в p4runtime_translation, хотя произвольные преобразования обязательны лишь для PortId_t, ClassOfService_t и других вариантов InHeader. Чтобы выполнить произвольное числовое преобразование для заданного типа, система P4Runtime ожидает URI (первый параметр p4runtime_translation) и нужного отображения.

5. Программируемые блоки

Ниже приведены шаблоны объявления программируемых блоков PSA. Разработчик программы P4 отвечает за реализацию элементов управления, соответствующих этим интерфейсам, и создание их экземпляров в определении пакета (package). Здесь используются одни пользовательские типы метаданных IM и заголовков IH для всех входных анализаторов и блоков управления. Выходной анализатор и блоки управления могут те же или иные типы по усмотренияю разработчика программы P4.

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

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

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

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

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

control EgressDeparser<H, M, CE2EM, RECIRCM>(
	packet_out buffer,
	out CE2EM clone_e2e_meta,
	out RECIRCM recirculate_meta,
	inout H hdr,
	in M meta,
	in psa_egress_output_metadata_t istd,
	in psa_egress_deparser_input_metadata_t edstd);
package IngressPipeline<IH, IM, NM, CI2EM, RESUBM, RECIRCM>(
	IngressParser<IH, IM, RESUBM, RECIRCM> ip,
	Ingress<IH, IM> ig,
	IngressDeparser<IH, IM, CI2EM, RESUBM, NM> id);

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

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

6. Описание путей пакетов

В разделе 3. Пути пакетов кратко перечислены пути пакетов, предоставляемые PSA и указаны их сокращенные имена, применяемые здесь.

6.1. Начальные значения пакетов, обрабатываемых входным конвейером

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

6.1.1. Исходное содержимое пакетов из портов

Для пакетов Ethernet поле packet_in пакетов в путях FP и NFCPU содержит кадр Ethernet, начиная с заголовка Ethernet Контрольная сумма (CRC) кадра Ethernet не включается10.

Таблица 3. Начальные значения для пакетов, обрабатываемых входным конвейером.

NFP

NFCPU

RESUB

RECIRC

packet_in

См. текст

user_meta

См. текст

Поля IngressParser istd (тип psa_ingress_parser_input_metadata_t)

ingress_port

Значение PortId_t входного порта для пакета

PSA_PORT_CPU

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

PSA_PORT_RECIRCULATE

packet_path

NORMAL

NORMAL

RESUBMIT

RECIRCULATE

Входные поля istd (тип psa_ingress_input_metadata_t)

ingress_port

То же значение, которое получено IngressParser (см. выше).

packet_path

То же значение, которое получено IngressParser (см. выше).

ingress_timestamp

Время начала обработки пакета в IngressParser. Для пакетов RESUB и RECIRC это время начала обработки «копии», а не оригинала.

parser_error

От IngressParser. Всегда error.NoError, если при анализе не возникло ошибок.

PSA не вносит дополнительных ограничений на packet_in.length() из спецификации P416. Не поддерживающие такой размер платформы должны обеспечивать механизмы информирования об ошибках.

В P4 Runtime имеется свойство Packet Out для отправки пакетов данных из контроллера в устройство PSA. Такие пакеты передаются в PSA как пакеты пути NFCPU. С этими пакетами не связано метаданных и они включают лишь содержимое, которое обрабатывается кодом IngressParser в программе P4 обычным способом. При этом может выполняться приведение типов полей заголовка, как описано в параграфе 4.4. Представление данных в плоскости управления и данных.

6.1.2. Исходное содержимое повторно представленных пакетов

Для пакетов RESUB в packet_in содержится то же, что и в packet_in до IngressParser для пакета, вызвавшего повторное представление данного пакета (т. е. без изменений, внесенных при входной обработке).

6.1.3. Исходное содержимое рециркулирующих пакетов

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

6.1.4. Пользовательские метаданные для всех входных пакетов

Архитектура PSA не требует инициализации пользовательских метаданных известными значениями перед отправкой пакета входному анализатору. Если пользовательская программа P4 явно инициализирует такие метаданные заранее (например, при старте анализатора), они будут проходить через анализатор в блок управления Ingress.

Имеется два направления в параметрах входного анализатора с пользовательскими типами — resubmit_meta и recirculate_meta. Они могут применяться для передачи метаданных в повтроно представляемых и рециркулированных пакетах.

Рассмотрим пакет, приходящий во входной конвейер и получающий в процессе обработки программой P4 значения стандартных полей метаданных PSA, приводящие к повторному представлению пакета (6.2. Поведение пакетов по завершении входной обработки). Во входном сборщике программа P4 задает значение выходного параметра resubmit_meta. Это значение (оно может содержать набор полей, структур, заголовков и т. п.) связывается реализацией PSA с повторно представляемым пакетом many individual values in fields, sub-structs, headers, etc.) becomes associated with the resubmitted packet by the PSA и при входном анализе повторно представленного пакета становится значением входного параметра resubmit_meta для входного анализатора. Для повторно представленных пакетов значение входного параметра recirculate_meta не определено.

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

Для пакетов из порта (включая CPU) входные параметры resubmit_meta и recirculate_meta не определены.

6.2. Поведение пакетов по завершении входной обработки

Приведенный ниже псевдокод управляет копированием (клонированием) пакетов по завершении работы блока управления Ingress на основе значений полей некоторых метаданных в структуре psa_ingress_output_metadata_t. Отмеченная ниже функция platform_port_valid() принимает значение типа PortId_t и возвращает, если это значение представляет выходной порт для реализации. Предполагается, что в некоторых реализациях PSA будут применяться битовые маски для значений PortId_t, не соответствующих какому-либо порту. Функция возвращает значение true для портов PSA_PORT_CPU и PSA_PORT_RECIRCULATE. Функция platform_port_valid не определена в PSA для вызова из программы P4 плоскости данных, поскольку нет известных вариантов ее вызова во время обработки пакета. Она предназначена для описания поведения в псевдокоде. Предполагается, что плоскость данных создает таблицы с действительными номерами портов.

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

struct psa_ingress_output_metadata_t {
	// В комментарии после каждого поля указано значение в момент
	// начала выполнения блока управления Ingress.
	ClassOfService_t	class_of_service;	// 0
	bool			clone;			// false
	CloneSessionId_t	clone_session_id; 	// не определено
	bool			drop;			// true
	bool			resubmit;		// false
	MulticastGroup_t	multicast_group; 	// 0
	PortId_t		egress_port;		// не определено
}

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

psa_ingress_output_metadata_t ostd;

if (ostd.clone) {
	Создается клон(ы) I2E с опциями, настроенными сеансом клонирования 
	PRE с номером ostd.clone_session_id;
} else { нет клонирования; }

if (ostd.drop) { отбрасывание пакета; }
else if (ostd.resubmit) { повторное представление пакета; }
else if (ostd.multicast_group != 0) { PRE multicast реплицирует пакет; }
else { PRE передает 1 копию пакета в ostd.egress_port; }

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

psa_ingress_output_metadata_t ostd;
if (ostd.clone) {
	if (значение ostd.clone_session_id поддерживается) {
		из значений, настроенных для ostd.clone_session_id в PRE {
			cos = class_of_service
			set((egress_port[0], instance[0]), ..., (egress_port[n], instance[n])) =
				набор пар egress_port и instance
			trunc = truncate
			plen = packet_length_bytes
		}
		if (значение cos не поддерживается) {
			cos = 0;
			// рекомендуется записать ошибку (не поддерживается значение cos).
		}
		Для каждой пары (egress_port, instance) в наборе {
			Создается клон пакета и передается в буфер пакетов с 
			egress_port, instance и class_of_service cos, после
			чего начинается выходная обработка. Клон будет включать 
			лишь первые plen байтов пакета, полученного входным
			анализатором, если trunc = true, и целый пакет в противном случае.
		}
	} else {
		// Клон не создается. Рекомендуется записать ошибку, связанную с 
		// не поддерживаемым значением ostd.clone_session_id.
	}
}
// Продолжение независимо от создания клона. Приведенный ниже код не оказывает
// влияния на созданные клоны.
if (ostd.drop) {
	Пакет отбрасывается.
	return;	// Последующие операции не выполняются.
}
if (значение ostd.class_of_service не поддерживается) {
	ostd.class_of_service = 0;	// Используется принятый по умолчанию класс 0
	// Рекомендуется записать ошибку, связанную с не поддерживаемым
	// значением ostd.class_of_service.
}
if (ostd.resubmit) {
	Пакет представляется повторно, возвращаясь во входной анализатор;
	return;	// Последующие операции не выполняются.
}
if (ostd.multicast_group != 0) {
	Могут создаваться копии пакета в соответствии с конфигурацией
	плоскости управления для multicast-группы ostd.multicast_group.
	Каждая копия будут иметь одинаковое значение ostd.class_of_service.
	return;	// Последующие операции не выполняются.
}
if (platform_port_valid(ostd.egress_port)) {
	Пакет помещается в очередь для выходного порта ostd.egress_port с классом
	обслуживания ostd.class_of_service.
} else {
	Пакет отбрасывается.
	// рекомендуется записать ошибку, связанную с не поддерживаемым ostd.egress_port.
}

Всякий раз, когда приведенный выше псевдокод направляет пакет по тому или иному пути, реализация PSA может при некоторых обстоятельствах отбросить пакет вместо его передачи. Например, причиной может служить нехватка буферов или какой-либо из механизмов контроля перегрузки, таких как RED11 или AFD12. Реализациям рекомендуется поддерживать счетчики отброшенных пакетов, предпочтительно раздельные для разных причин отбрасывания, поскольку некоторые из этих причин лежат за пределами ответственности программы P4.

Реализация PSA может поддерживать множество классов обслуживания для пакетов, передаваемых в буфер. В таких случаях блок управления Ingress может назначить для поля ostd.class_of_service значение, отличное от принятого по умолчанию (0).

PSA лишь задает, как блок управления Ingress может контролировать класс обслуживания для пакетов, но не диктует политику планирования для очередей, которые могут существовать в буфере пакетов. Для реализаций PSA с раздельными очередями для каждого класса обслуживания рекомендуется что-либо столь же гибкое, как взвешенная беспристрастная очередь. Рекомендации по упорядочению пакетов в устройствах PSA приведены в Приложении F. Упорядочение пакетов.

Спецификация P4 Runtime API определяет для контроллера способы определения числа различных классов обслуживания, поддерживаемых устройством PSA.

6.2.1. Групповая репликация

Плоскость управления может настроить для каждой группы multicast_group в PRE создание нужного числа копий для передачи в эту группу. Изначально каждая группа пуста и передача пакета в пустую группу ведет к его отбрасыванию. Плоскость управления может добавлять в группу одну или множество пар (egress_port, instance), а также может удалять имеющиеся пары из группы.

Предположим, что multicast-группа содержит приведенный ниже набор пар.

(egress_port[0], instance[0]),
(egress_port[1], instance[1]),
...,
(egress_port[N-1], instance[N-1])

При отправке пакета в эту группу создается N копий пакета. Копия с номером i, переданная на выходную обработку, будет иметь структуру типа psa_egress_input_metadata_t с полями egress_port = egress_port[i] и instance = instance[i].

Примечание. Группа представляет собой набор пар и от реализации не требуется создавать копии в порядке, который может задать плоскость управления. Рекомендации по упорядочению пакетов в устройствах PSA приведены в Приложении F. Упорядочение пакетов.

В одной multicast-группе все пары (egress_port, instance) должны отличаться друг от друга, но разрешено иметь совпадающее значение egress_port или instance в любом количестве пар. Любая пара (egress_port, instance) может входить в произвольное число multicast-групп.

Реализация PSA должна поддерживать лишь значения egress_port, представляющие одиночные порты устройства PSA, т. е. не требуется поддержка значений egress_port, представляющих интерфейсы LAG13, которые являются набором физических портов с распределением трафика.

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

6.3. Действия по направлению пакетов при входной обработке

Все описанные ниже действия меняют одно или несколько полей в структуре типа psa_ingress_output_metadata_t, которая является параметром inout для блока управления Ingress, и не оказывают иных непосредственных влияний. Судьба пакетов определяется значениями всех полей структуры по завершении входной обработки, а не в момент выполнения действий (см. параграф 6.2. Поведение пакетов по завершении входной обработки).

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

6.3.1. Индивидуальные операции

Отправка пакета в порт. Значения полей в начале выходной обработки пакета показаны в столбце NU таблицы 4.

/// Изменяются выходные метаданные входной обработки для отправки 
/// пакета на выходную обработку, а затем в egress_port (в процессе
/// выходной обработки пакет может быть отброшен). Это действие не 
/// влияет на операции клонирования и повторного представления.
action send_to_port(inout psa_ingress_output_metadata_t meta,
		     in PortId_t egress_port)
{
	meta.drop = false;
	meta.multicast_group = (MulticastGroup_t) 0;
	meta.egress_port = egress_port;
}

6.3.2. Групповые операции

Отправки пакета в multicast-группу или порт. Значения полей в начале выходной обработки пакета показаны в столбце NM таблицы 4.

Параметр multicast_group является идентификатором multicast-группы. Плоскость управления должна настраивать multicast-группы с помощью специального механизма, например, P4 Runtime API.

/// Изменение выходных метаданных входной обработки для создания копий
/// пакета, передаваемых на выходную обработку. Это действие не влияет
/// на операции клонирования и повторного представления.
action multicast(inout psa_ingress_output_metadata_t meta,
		  in MulticastGroup_t multicast_group)
{
	meta.drop = false;
	meta.multicast_group = multicast_group;
}

6.3.3. Отбрасывание пакета

Пакет не передается на обычную выходную обработку.

/// Изменение выходных метаданных входной обработки для отмены
/// обычной выходной обработки. Это действие не влияет на
/// клонирование пакетов, но предотвращает повторное представление.
action ingress_drop(inout psa_ingress_output_metadata_t meta)
{
	meta.drop = true;
}

6.4. Исходные значения пакетов, обрабатываемых выходным конвейером

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

Таблица 4. Начальные значения для пакетов, обрабатываемых выходным конвейером.

NU

NM

CI2E

CE2E

packet_in

См. текст

user_meta

См. текст

Поля EgressParser istd (тип psa_egress_parser_input_metadata_t)

egress_port

Значение ostd.egress_port во входном пакете

Из конфигурации PRE для группы

Из конфигурации PRE для сеанса клонирования

packet_path

NORMAL_UNICAST

NORMAL_MULTICAST

CLONE_I2E

CLONE_E2E

Выходные поля istd (psa_egress_input_metadata_t)

class_of_service

Значение ostd.class_of_service во входном пакете

Из конфигурации PRE для сеанса клонирования

egress_port

То же значение, которое получено EgressParser (см. выше).

packet_path

То же значение, которое получено EgressParser (см. выше).

instance

0

Из конфигурации PRE для группы

Из конфигурации PRE для сеанса клонирования

egress_timestamp

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

parser_error

От EgressParser. Всегда error.NoError, если при анализе не возникло ошибок.

6.4.1. Исходное содержимое обычных пакетов

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

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

6.4.2. Исходное содержимое клонов CI2E

Для пакетов CI2E значение packet_in берется из входного пакета, вызвавшего создание клона. Оно совпадает с содержимым packet_in входного пакета до IngressParser без изменений, внесенных входной обработкой. Поддерживается отсечка данных (payload) в пакетах.

Пакеты, клонированные во входном конвейере с сессией клонирования, где egress_port = PSA_PORT_RECIRCULATE, также относятся к этой категории.

6.4.3. Исходное содержимое клонов CE2E

Для пакетов CE2E значение packet_in берется из входного пакета, вызвавшего создание клона. Оно начинается с заголовков пакета, созданных выходным сборщиком, за которыми следует содержимое пакета, т. е. часть, не разбираемая выходным анализатором. Поддерживается отсечка данных (payload) в пакетах.

Пакеты, клонированные в выходном конвейере с сессией клонирования, где egress_port = PSA_PORT_RECIRCULATE, также относятся к этой категории.

6.4.4. Пользовательские метаданные для всех выходных пакетов

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

Основным отличием является использование для выходных пакетов иных путей. У выходного анализатора имеется 3 входных (in) параметра — normal_meta, clone_i2e_meta и clone_e2e_meta. Для каждого из пакетов в начале выходной обработки установлен один из этих параметров, а два оставшихся не определены.

Для пакетов NU и NM устанавливается лишь параметр normal_meta, принимая значение одноименного выходного (out) параметра входного сборщика при завершении входной обработки обычного пакета.

Для пакетов CLONE_I2E устанавливается лишь параметр clone_i2e_meta, принимая значение одноименного выходного (out) параметра входного сборщика при клонировании пакета.

Для пакетов CLONE_E2E устанавливается лишь параметр clone_e2e_meta, принимая значение одноименного выходного (out) параметра выходного сборщика при клонировании пакета.

6.4.5. Групповая адресация и клоны

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

  • egress_port — это поле обычно различается в разных копиях реплицированного пакета, но может совпадать у произвольного числа копий в соответствии с конфигурацией плоскости управления для PRE. Предполагается, что плоскость управления настраивает PRE так, чтобы у каждой копии исходного пакета была уникальная пара (egress_port, instance).
  • instance — см. egress_port.
  • egress_timestamp — это поле устанавливается независимо для каждой копии. В зависимости от объема трафика на каждом выходном порту значения могут существенно различаться у копий одного пакета.
  • parser_error — в общем случае значение этого поля будет совпадать у каждой копии одного реплицированного пакета. Однако в коде P4 для EgressParser поле определено независимо для каждой копии, поэтому при разном поведении, например, для разных egress_port значения parser_error также могут различаться.

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

6.5. Поведение пакетов по завершении выходной обработки

Приведенный ниже псевдокод определяет созданием копий пакетов по завершении работы блока управления Egress на основе содержимого нескольких полей метаданных в структуре psa_egress_output_metadata_t.

struct psa_egress_output_metadata_t {
	// В комментариях после полей указаны начальные значения по 
	// завершении работы блока управления Egress.
	bool			clone;			// false
	CloneSessionId_t	clone_session_id; 	// не определено
	bool			drop;			// false
}

psa_egress_input_metadata_t	istd;
psa_egress_output_metadata_t	ostd;

if (ostd.clone) {
	if (ostd.clone_session_id value is supported) {
		Из значений, настроенных для ostd.clone_session_id в PRE {
			cos = class_of_service
			set((egress_port[0], instance[0]), ..., (egress_port[n], instance[n])) =
				набор пар (egress_port, instance)
			trunc = truncate
			plen = packet_length_bytes
		}
		if (значение cos не поддерживается) {
			cos = 0;
			// Рекомендуется записать ошибку, связанную с
			// не поддерживаемым значением cos.
		}
		Для каждой пары (egress_port, instance) в наборе {
			Создается клон пакета и передается в буфер с полями
			egress_port, instance, class_of_service cos, после
			чего начинается выходная обработка. Клон будет включать
			не более plen начальных байтов пакета, полученного от
			выходного сборщика, при установке trunc = true и весь
			пакет в ином случае.
		}
	} else {
		// Клон не создается. Рекомендуется сделать запись об ошибке,
		// связанной с не поддерживаемым значением ostd.clone_session_id.
	}
}
// Продолжение не зависит от создания клона и не влияет на 
// созданные ранее клоны.
if (ostd.drop) {
	Отбрасывание пакета
	return;	// Последующие операции не выполняются.
}
// Значение istd.egress_port ниже совпадает со значением в начале 
// выходной обработки в соответствии с решением предшествующей
// входной обработки пакета (или задано конфигурацией PRE для
// сеанса клонирования независимо от создания клона на входе или
// выходе). Выходному коду не разрешается изменять его.
if (istd.egress_port == PSA_PORT_RECIRCULATE) {
	Рециркулировать пакет, возвращая его во входной анализатор;
	return;	// Последующие операции не выполняются.
}
Размещение пакета в очереди для выходного порта istd.egress_port

Как и при обработке пакета после входного конвейера, реализация PSA может отбрасывать пакеты после выходной обработке даже в случаях, когда приведенный выше псевдокод указывает их отправку. Например, могут отбрасываться клоны после выходной обработки, если буферы заполнены, или пакет представлен повторно на входную обработку в момент полной загрузки входного конвейера. Реализациям рекомендуется поддерживать счетчики отброшенных пакетов, предпочтительно независимые, поскольку отбрасывание пакетов может выходить за пределы ответственности программы P4.

6.6. Действия по направлению пакетов при выходной обработке

6.6.1. Отбрасывание пакета

Пакет не передается за пределы устройства по завершении выходной обработки.

/// Изменяются метаданные для отмены передачи пакета вовне.
/// Эта операция не влияет на поведение клонирования.
action egress_drop(inout psa_egress_output_metadata_t meta)
{
	meta.drop = true;
}

6.7. Содержимое передаваемого в порт пакета

С пакетами NTP и NTCPU не связывается метаданных.

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

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

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

P4 Runtime имеет свойство Packet In для приема пакетов, отправленных устройством PSA в порт PSA_PORT_CPU. С такими пакетами не связано метаданных и они включают лишь содержимое пакета, выдаваемое кодом EgressDeparser в программе P4. При этом могут выполняться некоторые преобразования полей заголовка, как описано в параграфе 4.1. Определения типов PSA.

6.8. Клонирование пакетов

Клонирование представляет собой механизм передачи пакетов в указанный порт в дополнение к передаче «обычного» пакета. Одна операция клонирования (clone) может создавать множество копий в зависимости от настройки плоскости управления.

Одним из применений клонирования является полное отображение трафика в другой порт (mirroring), т. е. обычная отправка пакетов адресату в соответствии с программой P4, сопровождаемая передачей копии пакета в другой порт, например, для системы мониторинга.

Клонирование пакетов происходит в конце входного и/или выходного конвейера. Семантика клонирования в PSA описана ниже. При вызове операции clone в конце входного конвейера каждый клон является копией пакета, попавшего во входной анализатор, а при вызове в конце выходного конвейера клоны являются копиями измененного пакета, получаемого от выходного сборщика. В обоих случаях клонированные пакеты попадают в выходной конвейер для дальнейшей обработки.

Логически PRE реализует механизмы копирования пакета. Клонированием управляют поля метаданных структур psa_ingress_output_metadata_t и psa_egress_output_metadata_t, имена которых начинаются с clone.

bool			clone;
CloneSessionId_t	clone_session_id;

Тег clone управляет клонированием пакета. При значении true клон пакета (пакетов) создается в конце конвейера. Поле clone_session_id указывает одну или несколько сессий клонирования, которые плоскость управления может настроить в PRE. Для каждой сессии клонирования плоскость управления может задать указанные ниже значения.

/// В каждой сессии клонирования могут создаваться пары (egress_port, instance).
PortId_t	egress_port; 	/// egress_port в паре (egress_port, instance).
EgressInstance_t instance; 	/// instance в паре (egress_port, instance).

/// Конфигурация сеанса клонирования задает в тоности 1 значение для указанных
/// ниже полей.
ClassOfService_t 	class_of_service;
bool			truncate;
PacketLength_t packet_length_bytes; /// Применяется только при truncate = true

Конфигурация набора пар (egress_port, instance) для сеанса клонирования похожа на конфигурацию пар для multicast-групп (6.2.1. Групповая репликация) в части требований и ограничений.

В качестве значения egress_port могут указываться любые порты, пригодные для обычных индивидуальных пакетов, например, обычные порты, PSA_PORT_CPU, PSA_PORT_RECIRCULATE. В двух последних случаях клоны будут передаваться в CPU или на рециркуляцию в конце выходной обработки как обычные индивидуальные пакеты.

Для сокращения расхода пропускной способности может применяться отсечка при клонировании пакетов с передачей лишь заданного числа начальных байтов пакета. Это может быть полезно при отправке некоторых пакетов плоскости управления, а также системам сбора данных для мониторинга трафика. Если для сессии установлено значение truncate = false, выполняется клонирование пакетов целиком. В противном случае клон включает лишь первые packet_length_bytes байтов исходного пакета. Отсечка пакетов не оказывает влияния на сопровождающие пакет метаданные и размер метаданных не учитывается в packet_length_bytes. Отсечка выполняется для исходного пакета, переданного в параметре packet_in входному анализатору (для клонов со входа на выход), или передаваемого наружу как параметр packet_out из выходного сборщика (для клонов с выхода на выход). Реализации PSA могут поддерживать лишь ограниченный диапазон значений packet_length_bytes, например, кратные 32 байтам.

Поскольку в общем случае предполагается клонирование пакетов в CPU, каждая реализация PSA начинается с сеанса клонирования PSA_CLONE_SESSION_TO_CPU, инициализируемого парой (egress_port, instance) с egress_port = PSA_PORT_CPU и instance = 0. Для этой сессии также устанавливается class_of_service = 0 и truncate = false.

6.8.1. Примеры клонов

Ниже приведен фрагмент кода для клонирования пакетов.

header clone_i2e_metadata_t {
	bit<8> custom_tag;
	EthernetAddress srcAddr;
}
control ingress(inout headers hdr,
		 inout metadata user_meta,
		 in psa_ingress_input_metadata_t istd,
		 inout psa_ingress_output_metadata_t ostd)
{
	action do_clone (CloneSessionId_t session_id) {
		ostd.clone = true;
		ostd.clone_session_id = session_id;
		user_meta.custom_clone_id = 1;
	}
	table t {
		key = {
			user_meta.fwd_metadata.outport : exact;
		}
		actions = { do_clone; }
	}
	apply {
		t.apply();
	}
}
control IngressDeparserImpl(packet_out packet,
			      out clone_i2e_metadata_t clone_i2e_meta,
			      out empty_metadata_t resubmit_meta,
			      out metadata normal_meta,
			      inout headers hdr,
			      in metadata meta,
			      in psa_ingress_output_metadata_t istd)
{
	DeparserImpl() common_deparser;
	apply {
		// Назначение выходного параметра clone_i2e_meta должно выполняться
		// при выполнении приведенного ниже условия.
		if (psa_clone_i2e(istd)) {
			clone_i2e_meta.custom_tag = (bit<8>) meta.custom_clone_id;
			if (meta.custom_clone_id == 1) {
				clone_i2e_meta.srcAddr = hdr.ethernet.srcAddr;
			}
		}
		common_deparser.apply(packet, hdr);
	}
}

6.9. Повторное представление пакетов

Повторное представление пакетов служит для повторения их обработки во входном конвейере и выполняется в конце этого конвейера. При повторном представлении пакета завершается его обработки во входном конвейере, после чего пакет снова представляется входному анализатору без выполнения сборки (deparse). Иными словами, повторно представленный пакет имеет тот же заголовок и данные, которые были у исходного пакета, сохраняется также значение ingress_port. Значение packet_path для повторно представленного пакета меняется на RESUBMIT.

Входной анализатор отличает повторно представленные пакеты от исходных по полю packet_path в метаданных ingress_parser_intrinsic_metadata_t. При анализе повторно представленного пакета может быть выбран другой алгоритм. Кроме того, входной конвейер может применить для такого пакета иное действие, нежели для исходного. Если платформа позволяет неоднократно выполнять повторное представление, пользовательская программа может различать такие пакеты по дополнительным метаданным, связанным с ними. Отметим, что максимальное симло повторов зависит от платформы (3. Пути пакетов).

PSA задает возможность использования операции resubmit лишь во входном конвейере, а выходной не может представлять пакеты повторно. Как указано в разделе 3, PSA не включает обязательного механизма предотвращения бесконечного повтора, рециркуляции или клонирования. Однако платформы могут задавать такие ограничения.

Одним из применений повторного представления пакетов является повышение емкости и гибкости конвейера обработки пакетов. Например, неоднократная обработка пакета во входном конвейере позволяет увеличить число применяемых к пакету операций в N раз, где N — число повторов представления пакета.

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

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

Реализации PSA поддерживают конфигурационный флаг resubmit для PRE, включающий механизм повторного представления. При установленном флаге исходный пакет представляется снова вместе с необязательными метаданными, созданными при первом прохождении. Если флаг сброшен (false), механизм повторного представления отключается и метаданные resubmit_meta не устанавливаются.

6.10. Рециркуляция пакетов

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

Вопрос рециркуляции пакета решается во время входной обработки путем его отправки в специальный порт PSA_PORT_RECIRCULATE. Сама рециркуляция происходит в конце выходного конвейера. При отправке пакета в порт рециркуляции его выходная обработка завершается (включая выходной сборщик) и пакет возвращается входному анализатору. Поле ingress_port при рециркуляции пакета получает значение PSA_PORT_RECIRCULATE, а packet_path — RECIRCULATE.

При рециркуляции также возможно связывание с пакетом дополнительных метаданных, которые создаются в процессе выходной обработки и передаются в выходном параметре recirculate_meta выходного сборщика. Эти метаданные доступны входному анализатору вместе с пакетом.

7. Внешние блоки PSA

7.1. Ограничения на использование внешних блоков

Все экземпляры объектов в программе P416 создаются при компиляции и могут быть организованы в дерево, которое будем называть деревом реализации. Корень этого дерева (T) представляет верхний уровень программы, а его потомками являются пакет PSA_Switch, описанный в разделе 5. Программируемые блоки, и все внешние блоки, созданные на верхнем уровне программы. Потомками узла PSA_Switch являются пакеты и внешние блоки, переданные как параметры экземпляра PSA_Switch. На рисунке 3 показано минимально возможное дерево для программы P4, использующей архитектуру PSA.

 
Рисунок 3. Дерево минимального экземпляра PSA.

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

Каждый экземпляр, узел которого является потомком узла Ingress в этом дереве, относится к экземпляру Ingress. (аналогично для других входных или выходных анализаторов и элементов управления). Все прочие экземпляры относятся к верхнему уровню.

Реализациям PSA разрешено отвергать программы, создающие или вызывающие внешние блоки с нарушением приведенных в таблице 5 условий.

Таблица 5. Элементы управления, которые могут создавать и вызывать внешние блоки.

 

Тип внешнего блока

Блоки, могущие создавать и вызывать extern

ActionProfile

Ingress, Egress

ActionSelector

Ingress, Egress

Checksum

IngressParser, EgressParser, IngressDeparser, EgressDeparser

Counter

Ingress, Egress

Digest

IngressDeparser

DirectCounter

Ingress, Egress

DirectMeter

Ingress, Egress

Hash

Ingress, Egress

InternetChecksum

IngressParser, EgressParser, IngressDeparser, EgressDeparser

Meter

Ingress, Egress

Random

Ingress, Egress

Register

Ingress, Egress

 

Например, ограничение Counter блоками Ingress, Egress означает, что каждый экземпляр Counter должен создаваться внутри блока управления Ingress или Egress, а также может наследоваться от этих узлов в дереве реализации. Например, при создании экземпляра Counter в блоке Ingress, счетчик нельзя указывать, а его методы вызывать из других блоков управления, не являющихся потомками Ingress в дереве.

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

Вызовы метода emit для типа packet_out могут в PSA размещаться лишь в блоках сборщиков, поскольку экзампляры packet_out видимы лишь в таких элементах управления. Точно также любые методы для типа packet_in (например, extract и advance) могут вызываться в программах PSA лишь из анализаторов. P416 ограничивает вызовы метода verify лишь синтаксическими анализаторами для всех программ P416, независимо от их предназначенности для PSA.

  • Предполагается, что высокопроизводительные реализации PSA не смогут обновлять один и тот же экземпляр extern из Ingress и Egress, а также из нескольких анализаторов или элементов управления, определенных в PSA.
  • В устройствах с несколькими конвейерами на деле имеется множество экземпляров входных и выходных конвейеров. Основной причиной создания множества конвейеров является практическая сложность доступа к одному объекту с поддержкой состояний (таблица, счетчик и т. п.) со скоростью, превышающей скорость пакетов в одном конвейере. Поэтому к объектам с поддержкой состояния следует обращаться лишь из одного конвейера в устройстве (см. Приложение E. Устройства PSA с несколькими конвейерами).

7.2. Свойства таблиц PSA

В таблице 6 указаны все свойства таблиц P4, определенные PSA, которые не включены в базовую спецификацию P416.

Таблица 6. Свойства таблиц PSA.

Свойство

Тип

Описание

psa_direct_counter

Имя одного экземпляра DirectCounter

7.7.3. Прямой счетчик

psa_direct_meter

Имя одного экземпляра DirectMeter

7.8. Измерители

psa_implementation

Имя одного экземпляра ActionProfile или ActionSelector

7.11. Профили действий, 7.12. Селекторы действий

psa_empty_group_action

action

7.12. Селекторы действий

psa_idle_timeout

PSA_IdleTimeout_t

7.2.1. Уведомление о тайм-ауте для записи таблицы

Реализации PSA недопустимо поддерживать оба свойства psa_implementation и psa_direct_counter в одной таблице. То же относится к одновременной поддержке свойств psa_implementation и psa_direct_meter.

7.2.1. Уведомление о тайм-ауте для записи таблицы

PSA использует свойство psa_idle_timeout для того, чтобы реализация таблицы могла передавать уведомления от устройства PSA по истечении заданного времени с момента последнего совпадения с записью таблицы. Это поле может принимать значение NO_TIMEOUT или NOTIFY_CONTROL. NO_TIMEOUT отключает уведомления и применяется по умолчанию, если свойство таблицы не задано. NOTIFY_CONTROL включает уведомления и реализация PSA будет генерировать API для плоскости управляния, позволяющий установить для записей таблицы срок действия (TTL), по истечении которого устройство будет передавать уведомление, если для записи не было найдено ни одного совпадения при поиске в таблице. Частота и режим генерации и доставки уведомлений плоскости управления определяются конфигурационными параметрами, задаваемыми API плоскости управления. Например,

enum PSA_IdleTimeout_t {
	NO_TIMEOUT,
	NOTIFY_CONTROL
}
table t {
	action a1 () { ... }
	action a2 () { ... }
	key = { hdr.f1: exact; }
	actions = { a1; a2; }
	default_action = a2;
	psa_idle_timeout = PSA_IdleTimeout_t.NOTIFY_CONTROL;
}

Для значений TTL и уведомлений имеется ряд ограничений, приведенных ниже.

  • Вероятно любая аппаратная реализация будет иметь ограниченное число битов для представления значений и, поскольку значения задаются в процессе работы, модулю runtime (P4Runtime или иной программе контроллера) разумно гарантировать возможность представления значений TTL в устройстве. Это можно сделать путем задания зависимости от доступного на платформе числа битов, чтобы обеспечить представления диапазона значений в разных записях. Реализациям PSA следует разрешать программирование лишь соответствующих таблиц и выдавать сообщение об ошибке, если устройство совсем не поддерживает тайм-аут бездействия. Если тайм-аут не задан для записи таблицы, уведомления не будут передаваться даже при включенном свойстве.
  • PSA не требует тайм-аута для записи с принятым по умолчанию действием, поскольку принятое по умолчанию действие может не иметь в таблице явной записи, а также по причине отсутствия убедительных причин использования контроллером информации о долгом отсутствии соответствий с конкретной таблицей. Запись для принятого по умолчанию действия никогда не устаревает.
  • В настоящее время таблицы, реализованные с использованием ActionSelector и ActionProfile, не поддерживают свойство psa_idle_timeout. Это ограничение может быть исключено из будущих версий спецификации.

7.3. Блок репликации пакетов

Внешний блок PacketReplicationEngine (PRE) представляет часть конвейера PSA, которая не программируется кодом P4. Хотя PRE невозможно программировать с помощью P4, этот блок можно настраивать с использованием API плоскости управления (например, путем настройки multicast-гупп и сессий clone). Для каждого пакета программа P4 обычно будет устанавливать значения внутренних метаданных в таких структурах, как psa_ingress_output_metadata_t и psa_egress_output_metadata_t, которые управляют операциями PRE над пакетом. В файле psa.p4 определены некоторые действия, помогающие установить значения этих полей в наиболее распространенных ситуациях, описанных в параграфах 6.3. Действия по направлению пакетов при входной обработке и 6.6. Действия по направлению пакетов при выходной обработке.

экземпляр внешнего блока PRE должен создаваться однократно при создании экземпляра пакета PSA_Switch. Определения пакета из файла psa.p4 приведены в конце раздела 5. Программируемые блоки. Ниже приведен пример создания экземпляров, включая один экземпляр PacketReplicationEngine и один экземпляр BufferingQueueingEngine при создании экземпляра пакета PSA_Switch.

IngressPipeline(IngressParserImpl(),
		 ingress(),
		 IngressDeparserImpl()) ip;

EgressPipeline(EgressParserImpl(),
		egress(),
		EgressDeparserImpl())ep;

PSA_Switch(ip, PacketReplicationEngine(), ep, BufferingQueueingEngine()) main;

7.4. Блок буферизации пакетов

Внешний блок BufferingQueueingEngine (BQE) представляет другую часть конвейера PSA (после выходной обработки), которая не программируется кодом P4. Хотя BQE невозможно программировать с использованием P4, этот блок можно настраивать напрямую через API плоскости управления или путем установки внутренних метаданных.

Экземпляр внешнего блока должен создаваться однократно, как и PRE. Дополнительное обсуждение и пример кода представлены в параграфе 7.3. Блок репликации пакетов.

7.5. Хэш

Ниже перечислены поддерживаемые алгоритмы хэширования.

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

7.5.1. Хэш-функция

Пример использования приведен ниже.

parser P() {
	Hash<bit<16>>(PSA_HashAlgorithm_t.CRC16) h;
	bit<16> hash_value = h.get_hash(buffer);
}

Параметры хэш-функции представлены ниже.

  • algo — используемый для расчета алгоритм (7.5. Хэш).

  • O — тип возвращаемого функцией значения.

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

7.6. Контрольные суммы

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

7.6.1. Базовая контрольная сумма

Базовый блок расчета контрольных сумм в PSA поддерживает произвольные алгоритмы хэширования и принимает 1 параметр:

  • W — размер контрольной суммы в битах.

extern Checksum<W> {
	/// Конструктор
	Checksum(PSA_HashAlgorithm_t hash);
	/// Сброс внутреннего состояния и подготовка блока к расчетам. Каждый
	/// экземпляр объекта Checksum инициализируется автоматически, как будто
	/// для него вызвана функция clear(). Инициализация выполняется при 
	/// создании каждого экземпляра, т. е. при каждом применении анализатора
	/// или элемента управления с объектом Checksum. Все состояния 
	/// поддерживаются объектом Checksum независимо для каждого пакета.
	void clear();
	/// Добавление данных в контрольную сумму.
	void update<T>(in T data);
	/// Получение контрольной суммы после добавления (но без удаления) данных
	/// с последнего вызова clear.
	W	get();
}

7.6.2. Инкрементная контрольная сумма

PSA поддерживает также инкрементальный расчет контрольных сумм, включающий дополнительный метод исключения данных, учтенных в предшествующем расчете. Контрольные суммы рассчитываются по алгоритму хеширования ONES_COMPLEMENT16, используемому протоколами IPv4, TCP и UDP (см. IETF RFC 1624 и Приложение B. Реализация внешнего блока InternetChecksum).

// Контрольные суммы на основе алгоритма ONES_COMPLEMENT16 применяются в IPv4, 
// TCP и UDP. Поддерживается инкрементное обновление с помощью метода subtract.
// См. IETF RFC 1624.
extern InternetChecksum {
	/// Конструктор
	InternetChecksum();
	/// Сброс внутреннего состояния и подготовка блока к расчетам. Каждый
	/// экземпляр объекта InternetChecksum инициализируется автоматически, 
	/// как будто для него вызвана функция clear(). Инициализация происходит 
	/// при создании каждого экземпляра, т. е. при каждом применении анализатора
	/// или элемента управления с этим объектом. Все состояния 
	/// поддерживаются объектом Checksum независимо для каждого пакета.
	void clear();
	/// Добавление данных в контрольную сумму. Размер data должен быть кратным 16.
	void add<T>(in T data);
	/// Исключение data из имеющейся контрольной суммы. Размер data должен быть
	/// кратным 16.
	void subtract<T>(in T data);
	/// Получение контрольной суммы после добавления (но без удаления) данных
	/// с последнего вызова clear.
	bit<16> get();
	/// Получение текущего статуса расчета контрольной суммы. Возвращаемое 
	/// значение предназначено лишь для будущих вызовоа метода set_state.
	bit<16> get_state();
	/// Восстанавливает состояние экземпляра InternetChecksum к одному из ранее
	/// возвращенных методом get_state. Это состояние может относиться к тому же
	/// или иному экземпляру внешнего блока InternetChecksum.
	void set_state(in bit<16> checksum_state);
}

7.6.3. Примеры InternetChecksum

Приведенный ниже фрагмент программы показывает использование внешнего блока InternetChecksum для проверки поля контрольной суммы в разобранном заголовке IPv4 и возврата ошибки анализатора при некорректном значении. Показаны также ошибки анализатора в блоке управления Ingress и отбрасывание пакетов в случае ошибки. Программы PSA могут обрабатывать пакеты с ошибками иначе, чем показано в примере и этот отдано на откуп разработчикам программ P4.

Ни P416, ни PSA не задают специальных механизмов записи вызвавшего ошибку анализатора места. Разработчик программы P4 может явно задать запись этой информации. Например, можно определить поля метаданных для этой цели, скажем, сохранять кодированное значение последнего состояния анализатора или число извлеченных байтов, а затем присваивать значения этих полей коду состояния анализатора.

// Определение дополнительных кодов ошибок для пакетов с некорректной 
// контрольной суммой заголовка IPv4.
error {
	UnhandledIPv4Options,
	BadIPv4HeaderChecksum
}
typedef bit<32> PacketCounter_t;
typedef bit<8> ErrorIndex_t;
const bit<9> NUM_ERRORS = 256;
parser IngressParserImpl(packet_in buffer,
			   out headers hdr,
			   inout metadata user_meta,
			   in psa_ingress_parser_input_metadata_t istd,
			   in empty_metadata_t resubmit_meta,
			   in empty_metadata_t recirculate_meta)
{
	InternetChecksum() ck;
	state start {
		buffer.extract(hdr.ethernet);
		transition select(hdr.ethernet.etherType) {
			0x0800: parse_ipv4;
			default: accept;
		}
	}
	state parse_ipv4 {
		buffer.extract(hdr.ipv4);
		// TBD: Было бы хорошо расширить этот пример для демонстрации
		// проверки контрольной суммы в заголовках IPv4 с опциями, но 
		// в данном примере не обрабатываются такие пакеты.
		verify(hdr.ipv4.ihl == 5, error.UnhandledIPv4Options);
		ck.clear();
		ck.add({
			/* 16-битовое слово 0	*/ hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv,
			/* 16-битовое слово 1	*/ hdr.ipv4.totalLen,
			/* 16-битовое слово 2	*/ hdr.ipv4.identification,
			/* 16-битовое слово 3	*/ hdr.ipv4.flags, hdr.ipv4.fragOffset,
			/* 16-битовое слово 4	*/ hdr.ipv4.ttl, hdr.ipv4.protocol,
			/* 16-битовое слово 5 skip hdr.ipv4.hdrChecksum, */
			/* 16-битовые слова 6-7 	*/ hdr.ipv4.srcAddr,
			/* 16-битовые слова 8-9 	*/ hdr.ipv4.dstAddr
			});
		// Оператор verify, приведенный ниже, будет переводить анализатор
		// в состояние reject, незамедлительно прерывая разбор при ошибке
		// в контрольной сумме заголовка IPv4. При этом записывается ошибка
		// error.BadIPv4HeaderChecksum, что будет доступно в поле метаданных
		// входного блока управления.
		verify(ck.get() == hdr.ipv4.hdrChecksum,
			error.BadIPv4HeaderChecksum);
		transition select(hdr.ipv4.protocol) {
			6: parse_tcp;
			default: accept;
		}
	}
	state parse_tcp {
		buffer.extract(hdr.tcp);
		transition accept;
	}
}
control ingress(inout headers hdr,
		 inout metadata user_meta,
		 in psa_ingress_input_metadata_t istd,
		 inout psa_ingress_output_metadata_t ostd)
{
	// Таблица parser_error_count_and_convert, приведенная ниже, показывает
	// один из способов учета числа разных ошибок анализа. Хотя в примере
	// это не используется, показано также преобразование кода ошибки в 
	// уникальное значение вектора битов error_idx, который может быть 
	// полезен для кодирования ошибок в заголовке пакета (например, при 
	// отправке CPU).
	DirectCounter<PacketCounter_t>(PSA_CounterType_t.PACKETS) parser_error_counts;
	ErrorIndex_t error_idx;
	action set_error_idx (ErrorIndex_t idx) {
		error_idx = idx;
		parser_error_counts.count();
	}
	table parser_error_count_and_convert {
		key = {
			istd.parser_error : exact;
		}
		actions = {
			set_error_idx;
		}
		default_action = set_error_idx(0);
		const entries = {
			error.NoError			: set_error_idx(1);
			error.PacketTooShort		: set_error_idx(2);
			error.NoMatch			: set_error_idx(3);
			error.StackOutOfBounds		: set_error_idx(4);
			error.HeaderTooShort		: set_error_idx(5);
			error.ParserTimeout		: set_error_idx(6);
			error.BadIPv4HeaderChecksum 	: set_error_idx(7);
			error.UnhandledIPv4Options 	: set_error_idx(8);
		}
		psa_direct_counter = parser_error_counts;
	}
	apply {
		if (istd.parser_error != error.NoError) {
			// Пример кода, учитывающего каждый тип ошибок анализатора.
			parser_error_count_and_convert.apply();
			ingress_drop(ostd);
			exit;
		}
		// Здесь выполняется обычная обработка пакета.
	}
}

Ниже приведен фрагмент программы, показывающий использование внешнего блока InternetChecksum для расчета и заполнения поля контрольной суммы IPv4 в блоке сборщика. В этом примере контрольная сумма обновляется и результат будет корректным независимо от изменений полей заголовка IPv4 в предшествующем блоке управления Ingress (или Egress).

control EgressDeparserImpl(packet_out packet,
			     out empty_metadata_t clone_e2e_meta,
			     out empty_metadata_t recirculate_meta,
			     inout headers hdr,
			     in metadata meta,
			     in psa_egress_output_metadata_t istd,
			     in psa_egress_deparser_input_metadata_t edstd)
{
	InternetChecksum() ck;
	apply {
		ck.clear();
		ck.add({
			/* 16-битовое слово 0	*/ hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv,
			/* 16-битовое слово 1	*/ hdr.ipv4.totalLen,
			/* 16-битовое слово 2	*/ hdr.ipv4.identification,
			/* 16-битовое слово 3	*/ hdr.ipv4.flags, hdr.ipv4.fragOffset,
			/* 16-битовое слово 4	*/ hdr.ipv4.ttl, hdr.ipv4.protocol,
			/* 16-битовое слово 5 пропуск hdr.ipv4.hdrChecksum, */
			/* 16-битовые слова 6-7 	*/ hdr.ipv4.srcAddr,
			/* 16-битовые слова 8-9 	*/ hdr.ipv4.dstAddr
			});
		hdr.ipv4.hdrChecksum = ck.get();
		packet.emit(hdr.ethernet);
		packet.emit(hdr.ipv4);
		packet.emit(hdr.tcp);
	}
}

В качестве финального примера показано использование InternetChecksum для инкрементного расчета контрольной суммы TCP. Напомним, что контрольная сумма TCP рассчитывается для всего пакета, включая заголовок. Поскольку тело пакета не доступно реализации PSA, предполагается, что контрольная сумма TCP для исходного пакета корректна и значение обновляется инкрементально вызовом методов subtract и add для всех полей, измененных программой. Например, блок управления Ingress в приведенной ниже программе меняет адрес отправителя IPv4, записывая исходный адрес в поле метаданных.

control ingress(inout headers hdr,
		 inout metadata user_meta,
		 in psa_ingress_input_metadata_t istd,
		 inout psa_ingress_output_metadata_t ostd) {
	action drop() {
		ingress_drop(ostd);
	}
	action forward(PortId_t port, bit<32> srcAddr) {
		user_meta.fwd_metadata.old_srcAddr = hdr.ipv4.srcAddr;
		hdr.ipv4.srcAddr = srcAddr;
		send_to_port(ostd, port);
	}
	table route {
		key = { hdr.ipv4.dstAddr : lpm; }
		actions = {
			forward;
			drop;
		}
	}
	apply {
		if(hdr.ipv4.isValid()) {
			route.apply();
		}
	}
}

Сборщик сначала обновляет контрольную суммы IPv4, как показано выше, затем инкрементально рассчитывает контрольную сумму TCP.

control EgressDeparserImpl(packet_out packet,
			     out empty_metadata_t clone_e2e_meta,
			     out empty_metadata_t recirculate_meta,
			     inout headers hdr,
			     in metadata user_meta,
			     in psa_egress_output_metadata_t istd,
			     in psa_egress_deparser_input_metadata_t edstd)
{
	InternetChecksum() ck;
	apply {
		// Обновление контрольной суммы IPv4. Этот вызов clear() 
		// можно удалить, поскольку экземпляр InternetCheckum 
		// автоматически сбрасывается для каждого пакета.
		ck.clear();
		ck.add({
			/* 16-битовое слово 0	*/ hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv,
			/* 16-битовое слово 1	*/ hdr.ipv4.totalLen,
			/* 16-битовое слово 2	*/ hdr.ipv4.identification,
			/* 16-битовое слово 3	*/ hdr.ipv4.flags, hdr.ipv4.fragOffset,
			/* 16-битовое слово 4	*/ hdr.ipv4.ttl, hdr.ipv4.protocol,
			/* 16-битовое слово 5 пропуск hdr.ipv4.hdrChecksum, */
			/* 16-битовые слова 6-7 	*/ hdr.ipv4.srcAddr,
			/* 16-битовые слова 8-9 	*/ hdr.ipv4.dstAddr
			});
		hdr.ipv4.hdrChecksum = ck.get();
		// Обновление контрольной суммы TCP. Этот вызов clear() нужен, 
		// поскольку повторно используется тот же экземпляр ck для 
		// того же пакета. Если применить взамен другой экземпляр 
		// InternetChecksum вместо ck, вызов clear() не нужен.
		ck.clear();
		// Вычитание исходной контрольной суммы TCP
		ck.subtract(hdr.tcp.checksum);
		// Учет удаления исходного адреса отправителя IPv4, являющегося
		// частью псевдозаголовка TCP для расчета контрольной суммы 
		// TCP (см. RFC 793), затем учет влияния нового адреса отправителя
		// отправителя IPv4.
		ck.subtract(user_meta.fwd_metadata.old_srcAddr);
		ck.add(hdr.ipv4.srcAddr);
		hdr.tcp.checksum = ck.get();
		packet.emit(hdr.ethernet);
		packet.emit(hdr.ipv4);
		packet.emit(hdr.tcp);
	}
}

7.7. Счетчики

Счетчики обеспечивают механизм сбора статистики и плоскость управления может считывать значения счетчиков, а программам P4 разрешено лишь обновлять показания счетчиков. Если нужно реализовать свойства, включающие порядковые номера пакетов, для этого следует применять регистры (7.9. Регистры).

Прямыми (direct counter) называют счетчики, связанные с конкретной таблицей P4 и реализованные с помощью внешнего блока DirectCounter. Кроме того, имеются индексированные счетчики, которые реализуются с помощью внешнего блока Counter. Основные различия между прямыми и индексированными счетчиками указаны ниже.

  • Число независимо обновляемых значений счетчиков:
    • один экземпляр прямого счетчика всегда содержит столько независимых значений, сколько записей имеется в таблице, связанной с этим счетчиком;
    • для индексированного счетчика число независимых значений можно задать при создании экземпляра и эти числа могут отличаться от числа записей в таблицах.
  • Обновление значений счетчиков в программе P4:
    • для прямых счетчиков вызов метода count доступен лишь из действий в таблице, с которой связан счетчик, поэтому значения счетчиков обновляются при совпадении с соответствующей записью таблицы;
    • для индексированных счетчиков метод count можно вызывать из любого места программы P4, где разрешены вызовы внешних объектов (например, в действии или напрямую в блоке apply элемента управления) и при каждом вызове метода должен указываться индекс обновляемого значения.

Счетчики предназначены для учета пакетов или байтов, а также комбинации тех и других (PACKETS_AND_BYTES). Счетчики байтов всегда увеличиваются на размер пакета, но учитываемые в размере поля могут отличаться в разных реализациях PSA. Например, одна реализация может использовать размер кадра Ethernet, включая заголовок Ethernet и байты FCS при получении пакета физическим портом, а другая — не включать байты FCS в размер пакета. Каждой реализации PSA следует указывать используемый для счетчиков байтов размер пакетов.

Если нужен подсчет других величин или более точный учет размера пакетов в счетчиках байтов, можно воспользоваться для этого регистрами (7.9. Регистры).

7.7.1. Типы счетчиков

enum PSA_CounterType_t {
	PACKETS,
	BYTES,
	PACKETS_AND_BYTES
}

7.7.2. Непрямой счетчик

/// Непрямой счетчик с n_counters независимых значений, где каждое
/// значение имеет в плоскости данных размер, заданный типом W.
extern Counter<W, S> {
	Counter(bit<32> n_counters, PSA_CounterType_t type);
	void count(in S index);
	/*
	/// API плоскости управления использует 64-битовые значения счетчиков. 
	/// Это не отражает размеры счетчиков в плоскости данных.
	/// Предполагается, что программы плоскости управления периодически
	/// считывают значения счетчиков плоскости данных и собирают их в
	/// более крупных счетчиках, которые позволяют учет в течение 
	/// продолжительного времени без переполнения. 64-битовые счетчики
	/// при скорости интерфейсов 100 Гбит/с будут переполняться 
	/// примерно через 46 лет.
	@ControlPlaneAPI
	{
		bit<64> read		(in S index);
		bit<64> sync_read	(in S index);
		void set		(in S index, in bit<64> seed);
		void reset		(in S index);
		void start		(in S index);
		void stop		(in S index);
	}
	*/
}

Псевдокод внешнего блока Counter приведен в Приложении C. Пример реализации внешнего блока Counter.

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

7.7.3. Прямой счетчик

extern DirectCounter<W> {
	DirectCounter(PSA_CounterType_t type);
	void count();
	/*
	@ControlPlaneAPI
	{
		W	read<W>		(in TableEntry key);
		W	sync_read<W>	(in TableEntry key);
		void 	set		(in TableEntry key, in W seed);
		void 	reset		(in TableEntry key);
		void 	start		(in TableEntry key);
		void 	stop		(in TableEntry key);
	}
	*/
}

Экземпляр DirectCounter должен присутствовать как значение атрибута таблицы psa_direct_counter не более чем в 1 таблице, которую будем называть владельцем экземпляра DirectCounter. Вызов метода count для экземпляра DirectCounter за пределами владельца является ошибкой.

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

Реализация внешнего блока DirectCounter практически совпадает с реализацией Counter. Отсутствие индекса как параметра метода count позволяет исключить проверку корректности значения индекса.

Приведенные здесь правила означают, что действием, вызывающим count для экземпляра DirectCounter, может быть лишь действие владеющей экземпляром таблицы. Если нужно действие A, которое можно вызывать из разных таблиц, это можно реализовать создавая уникальное действие для каждой таблицы с DirectCounter, которое в свою очередь будет вызывать действие A в дополнение к вызову count.

Экземпляр DirectCounter должен иметь, связанное с таблицей-владельцем, значение счетчика, которое обновляется при выполнении принятого по умолчанию действия в случае отсутствия совпадений при поиске в таблице (miss). Если таблица не имеет принятого по умолчанию действия, при отсутствии совпадений (miss) никакие из связанных с таблицей счетчиков не обновляются. Принятым по умолчанию действием таблицы считается действие, заданное в программе P4 для свойства таблицы default_action или явно назначенное для таблицы плоскостью управления. В остальных случаях у таблицы не будет используемого по умолчанию действия.

7.7.4. Пример использования счетчиков

Приведенный ниже фрагмент программы P4 демонстрирует создание экземпляров и обновление значений для внешних блоков Counter и DirectCounter.

typedef bit<48> ByteCounter_t;
typedef bit<32> PacketCounter_t;
typedef bit<80> PacketByteCounter_t;
const bit<32> NUM_PORTS = 512;
struct headers {
	ethernet_t	ethernet;
	ipv4_t		ipv4;
}
control ingress(inout headers hdr,
		 inout metadata user_meta,
		 in psa_ingress_input_metadata_t istd,
		 inout psa_ingress_output_metadata_t ostd)
{
	Counter<ByteCounter_t, PortId_t>(NUM_PORTS, PSA_CounterType_t.BYTES)
		port_bytes_in;
	DirectCounter<PacketByteCounter_t>(PSA_CounterType_t.PACKETS_AND_BYTES)
		per_prefix_pkt_byte_count;
	action next_hop(PortId_t oport) {
		per_prefix_pkt_byte_count.count();
		send_to_port(ostd, oport);
	}
	action default_route_drop() {
		per_prefix_pkt_byte_count.count();
		ingress_drop(ostd);
	}
	table ipv4_da_lpm {
		key = { hdr.ipv4.dstAddr: lpm; }
		actions = {
			next_hop;
			default_route_drop;
		}
	default_action = default_route_drop;
	// Таблица ipv4_da_lpm владеет этим экземпляром DirectCounter.
	psa_direct_counter = per_prefix_pkt_byte_count;
} apply {
	port_bytes_in.count(istd.ingress_port);
	if (hdr.ipv4.isValid()) {
	ipv4_da_lpm.apply();
}
control egress(inout headers hdr,
		inout metadata user_meta,
		in psa_egress_input_metadata_t istd,
		inout psa_egress_output_metadata_t ostd)
{
	Counter<ByteCounter_t, PortId_t>(NUM_PORTS, PSA_CounterType_t.BYTES)
		port_bytes_out;
	apply {
		// Это обновление выполняется на выходе, поскольку групповая
		// репликация выполняется до выходной обработки. В результате
		// обновление будет выполняться для каждой копии.
		port_bytes_out.count(istd.egress_port);
	}
}

7.8. Измерители

Измерители (RFC 2698) обеспечивают более сложные механизмы сбора статистики по сравнению со счетчиками. Их чаще всего применяют для маркировки или отбрасывания пакетов, для которых скорость (в пакетах или битах) превышает среднее значение. Маркировка пакета означает изменение одного или нескольких параметров качества обслуживания в заголовках пакетов, таких как 802.1Q PCP14 или биты DSCP15 в байте типа обслуживания IPv4 или IPv6. Заданные в PSA измерители являются «трехцветными».

От измерителей PSA не требуется выполнение действий по отбрасыванию или маркировке и они не выполняют таких действий автоматически. Измерители сохраняют и обновляют состояние при вызове метода execute(), возвращая значение GREEN (соответствие), YELLOW (избыток) или RED (нарушение). Условия возврата того или иного значения описаны в RFC 2698. Программа P4 отвечает за проверку возвращаемых значений и изменение поведения пакетов в соответствии с результатом. При инициализации измерителей устанавливается значение GREEN (спецификация P4).

В RFC 2698 рассмотрены осведомленные (color aware) и «слепые» (color blind) варианты измерителей. Внешние блоки Meter и DirectMeter реализуют оба варианта. Единственным различием является метод обновления, как отмечено в комментариях к приведенному ниже коду.

Подобно счетчикам, измерители делятся на прямые и индексированные. Непрямые измерители указываются индексом, а прямые связаны с записями таблиц и обновляются при совпадении записи с ключом поиска. Из API плоскости управления доступ к прямым измерителям выполняет P4 Runtime по записи таблицы в качестве ключа.

Между счетчиками и измерителями имеется много общего, как показано ниже.

  • Число независимых обновляемых значения.
  • Точки возможного обновления значений из программы P4.
  • Для измерителей типа BYTES при обновлении используется размер пакета в соответствии с реализацией PSA (подход реализаций может различаться).

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

  • Метод execute для DirectMeter должен вызываться из действия, вызванного таблицей, которая владеет экземпляром DirectMeter. Действия не обязаны вызывать метод execute, но могут делать это.
  • Должно быть состояние измерителя для экземпляра DirectMeter, обновляемое при отсутствии совпадений (miss) в таблице-владельце. Как и для DirectCounter, это состояние требуется лишь в таблицах, включающих принятое по умолчанию действие.

Атрибутом таблицы, указывающим, что она владеет экземпляром DirectMeter, является psa_direct_meter. Значением этого атрибута таблицы является имя экземпляра DirectMeter.

Если для измерителя вызывается execute(idx) со значением idx, выходящим за пределы диапазона индексов, состояние измерителя не меняется (как и для счетчиков). Вызов execute возвращает значение PSA_MeterColor_t, но оно не определено. Программе, желающие обеспечить предсказуемое поведение должны предотвращать влияние таких значений на выходные пакеты и возникновение иных побочных эффектов. В приведенном ниже примере кода показан один из способов обеспечить предсказуемое поведение. Отметим, что неопределенностей в поведении не возникает, если значение n_meters для индексированного измерителя равно 2W, а использованный для создания измерителя тип S представляет собой bit<W> (в таких случаях индекс просто не выходит за границы диапазона).

#define METER1_SIZE 100
Meter<bit<7>>(METER1_SIZE, PSA_MeterType_t.BYTES) meter1;
bit<7> idx;
PSA_MeterColor_t color1;
// ... later ...
if (idx < METER1_SIZE) {
	color1 = meter1.execute(idx, PSA_MeterColor_t.GREEN);
} else {
	// Если idx выходит за границы диапазона, используется принятое
	// по умолчанию значение для color1. Можно также сохранять флаг
	// ошибки в поле метаданных.
	color1 = PSA_MeterColor_t.RED;
}

Для любой реализации диапазон значений Peak Burst Size и Committed Burst Size является конечным и в документации следует указывать поддерживаемые размеры пиков. Если реализация выполняет внутреннюю отсечку запрашиваемых плоскостью управления значений, это также следует указать в документации. В качестве максимального размера пиков рекомендуется устанавливать значение не меньше числа байтов, передаваемых с максимальной скоростью порта в течение 100 мсек.

Реализации могут также ограничивать диапазон и точность значений Peak Information Rate и Committed Information Rate. В документации следует указывать максимальное поддерживаемое значение, а также точность задания значений. Рекомендуется ограничивать максимально поддерживаемую скорость значением не меньше скорости наиболее быстрого порта, а фактическую скорость устанавливать с отклонением в пределах 0,1% от запрошенной.

7.8.1. Типы измерителей

enum PSA_MeterType_t {
	PACKETS,
	BYTES
}

7.8.2. Цвета для измерителей

enum PSA_MeterColor_t { RED, GREEN, YELLOW }

7.8.3. Непрямой измеритель

// Индексированный измеритель с n_meters независимых значений.
extern Meter<S> {
	Meter(bit<32> n_meters, PSA_MeterType_t type);
	// Этот метод вызывается для осведомленных о цветах измерителей 
	// (см. RFC 2698). Цвет пакета перед вызовом метода указывается
	// параметром color.
	PSA_MeterColor_t execute(in S index, in PSA_MeterColor_t color);
	// Этот метод вызывается для обновления «слепого» измерителя
	// (см. RFC 2698). Его можно реализовать вызовом execute(index,
	// MeterColor_t.GREEN), обеспечивающим такое же поведение.
	PSA_MeterColor_t execute(in S index);
	/*
	@ControlPlaneAPI
	{
		reset(in MeterColor_t color);
		setParams(in S index, in MeterConfig config);
		getParams(in S index, out MeterConfig config);
	}
	*/
}

7.8.4. Прямой измеритель

extern DirectMeter {
	DirectMeter(PSA_MeterType_t type);
	// См. описание методов для Meter.
	PSA_MeterColor_t execute(in PSA_MeterColor_t color);
	PSA_MeterColor_t execute();
	/*
	@ControlPlaneAPI
	{
		reset(in TableEntry entry, in MeterColor_t color);
		void setConfig(in TableEntry entry, in MeterConfig config);
		void getConfig(in TableEntry entry, out MeterConfig config);
	}
	*/
}

7.9. Регистры

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

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

Для экземпляров Register имеется два разных конструктора. Значения, возвращаемое для неициализированного варианта, будет неопределенным, а для инициализированного — заданным параметром конструктора initial_value.

Простым примером использования служит фиксация обнаружения «первого» пакета определенного типа потока. Выделяемая для потока ячейка регистра инициализируется пустой, а при обнаружении протоколом первого пакета для этого потока таблица даст совпадение и ячейка регистра перейдет в состояние marked. Следующие пакеты потока будут сопоставляться с этой же ячейкой, а текущее значение ячейки будет сохраняться в метаданных для пакета, чтобы последующие таблицы могли проверить наличие маркера у потока.

extern Register<T, S> {
	/// Создание массива из <size> регистров. Начальные значения не 
	/// определены.
	Register(bit<32> size);
	/// Инициализация массива из <size> регистров с установкой 
	/// начального значения initial_value.
	Register(bit<32> size, T initial_value);
	T	read 	(in S index);
	void 	write 	(in S index, in T value);
	/*
	@ControlPlaneAPI
	{
		T	read<T>	(in S index);
		void 	set	(in S index, in T seed);
		void 	reset	(in S index);
	}
	*/
}

Ниже приведен другой пример с учетом пакетов и байтов, где счетчики могут обновляться размером пакетов, определяемым программой P4, а не заданным реализацией PSA.

const bit<32> NUM_PORTS = 512;
// Более удобно применять для представления комбинированного счетчика 
// пакетов и байтов, а также других комбинированных значений тип struct 
// и хранить значения в экземпляре Register. Однако версия p4test от
// 10.02.2018 не позволяет возвращать тип struct из вызовов, подобных
// Register.read(). Этот вопрос обсуждается на Github (см. 
// https://github.com/p4lang/p4-spec/issues/383) 
#define PACKET_COUNT_WIDTH 32
#define BYTE_COUNT_WIDTH 48
//#define PACKET_BYTE_COUNT_WIDTH (PACKET_COUNT_WIDTH + BYTE_COUNT_WIDTH)
#define PACKET_BYTE_COUNT_WIDTH 80
#define PACKET_COUNT_RANGE (PACKET_BYTE_COUNT_WIDTH-1):BYTE_COUNT_WIDTH
#define BYTE_COUNT_RANGE (BYTE_COUNT_WIDTH-1):0
typedef bit<PACKET_BYTE_COUNT_WIDTH> PacketByteCountState_t;
action update_pkt_ip_byte_count (inout PacketByteCountState_t s,
				   in bit<16> ip_length_bytes)
{
	s[PACKET_COUNT_RANGE] = s[PACKET_COUNT_RANGE] + 1;
	s[BYTE_COUNT_RANGE] = (s[BYTE_COUNT_RANGE] +
		(bit<BYTE_COUNT_WIDTH>) ip_length_bytes);
}
control ingress(inout headers hdr,
		 inout metadata user_meta,
		 in psa_ingress_input_metadata_t istd,
		 inout psa_ingress_output_metadata_t ostd)
{
	Register<PacketByteCountState_t, PortId_t>(NUM_PORTS)
		port_pkt_ip_bytes_in;
	apply {
		ostd.egress_port = (PortId_t) 0;
		if (hdr.ipv4.isValid()) {
			@atomic {
				PacketByteCountState_t tmp;
				tmp = port_pkt_ip_bytes_in.read(istd.ingress_port);
				update_pkt_ip_byte_count(tmp, hdr.ipv4.totalLen);
				port_pkt_ip_bytes_in.write(istd.ingress_port, tmp);
			}
		}
	}
}

Отметим использование аннотации @atomic в блоке, включающем вызовы методов read() и write() экземпляра Register. Считается, что в общем случае при доступе к регистрам нужна аннотация @atomic для блока кода, чтобы обеспечить нужное поведение. Как указано в спецификации P416, без аннотации @atomic в этом примере реализация может параллельно обрабатывать два пакета P1 и P2 с доступом к регистрам в показанном ниже порядке.

	// Возможный порядок операций для программы без аннотации @atomic.
	tmp = port_pkt_ip_bytes_in.read(istd.ingress_port);	// Для пакета P1
	tmp = port_pkt_ip_bytes_in.read(istd.ingress_port);	// Для пакета P2
	// Здесь пакеты P1 и P2 приходят из одного ingress_port и значения
	// tmp для них совпадают.
	update_pkt_ip_byte_count(tmp, hdr.ipv4.totalLen);	// Для пакета P1
	update_pkt_ip_byte_count(tmp, hdr.ipv4.totalLen);	// Для пакета P2
	port_pkt_ip_bytes_in.write(istd.ingress_port, tmp);	// Для пакета P1
	port_pkt_ip_bytes_in.write(istd.ingress_port, tmp);	// Для пакета P2
	// Операция write() для пакета P1 будет потеряна.

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

Вызовы отдельных методов для счетчиков и измерителей не нужно включать в блоки @atomic, поскольку для них гарантирована неделимость без потери внесенных изменений. Хотя спецификация P416 v1.0.0 требует от каждого действия (action) в таблице неделимого поведения, как при использовании аннотации @atomic для всего действия, рекомендуется явно использовать аннотации @atomic внутри тела действий, поскольку (a) это безопасно и (b), что более важно, упомянутое требование может быть исключено в будущих версиях спецификации.

Как и для индексированных измерителей и счетчиков, доступ к индексируемым регистрам должен выполняться с соблюдением границ значений индекса. Запись в регистр с выходящим за границу индексом не будет давать результата, а чтение вернет неопределенное значение. В примере параграфа 7.8. Измерители показан код, гарантирующий предотвращение упомянутых неопределенностей. Выход за границы индекса регистра становится невозможным, если экземпляр регистра объявлен с типом S как bit<W> и размером 2W.

7.10. Случайные числа

Внешний блок Random обеспечивает генерацию псевдослучайных чисел из заданного диапазона с однородным распределением. Если нужно неоднородное распределение создаваемых значений, можно воспользоваться созданным значением с однородным распределением, а затем использовать поиск в и/или арифметическую операцию над результатом для получения желаемого распределения. От реализаций не требуется создание криптостойких псевдослучайных значений. Например, в недорогой реализации может применяться регистр сдвига с линейной обратной связью.

extern Random<T> {
	/// Возвращает случайное значение из диапазона [min, max]. Реализациям
	/// разрешено поддерживать диапазоны, где значение (max - min + 1)
	/// является степенью 2. Разработчикам P4 следует ограничивать значения
	/// аргументов соответствующими числами для переносимости программ.
	Random(T min, T max);
	T read();
	/*
	@ControlPlaneAPI
	{
		void reset();
		void setSeed(in T seed);
	}
	*/
}

7.11. Профили действий

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

 
Рисунок 4. Профили действий в PSA.

На рисунке 4 сравниваются прямые (direct) таблицы и таблицы с реализацией профиля действий. Прямая таблица, как показано на рисунке 4 (a), содержит спецификацию действия в каждой своей записи. На рисунке показан пример таблицы LPM с полем заголовка h.f в качестве ключа поиска. Действием служит указание порта. Видно, что записи t1 и t3 имеют одно действие, т. е. устанавливают порт 1. Профили действий позволяют совместно использовать действие в записях разных таблиц, как показано на рисунке 4(b). Таблица с реализацией профиля действий имеет записи, указывающие элемент профиля вместо спецификации конкретного действия. Сопоставление элементов профиля со спецификациями действий поддерживается в отдельной таблице, которая является частью заданного профиля действий. При использовании таблицы с реализацией профиля действий ссылка на элемент профиля преобразуется в спецификацию конкретных действий, применяемых к пакету.

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

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

extern ActionProfile {
	/// Создание профиля действий с числом элементов size.
	ActionProfile(bit<32> size);
	/*
	@ControlPlaneAPI
	{
		entry_handle 	add_member	(action_ref, action_data);
		void		delete_member 	(entry_handle);
		entry_handle 	modify_member 	(entry_handle, action_ref, action_data);
	}
	*/
}

7.11.1. Пример профиля действий

Блок управления P4 Ctrl в приведенном ниже примере создает экземпляр профиля действий ap, который может включать до 128 элементов. Таблица indirect применяет этот экземпляр путем установки атрибута psa_implementation. Плоскость управления может добавлять элементы в ap и каждый элемент может задавать действие foo или NoAction. Записи таблицы indirect должны указывать действия, используя заданные контроллером ссылки на элементы профиля.

control Ctrl(inout H hdr, inout M meta) {
	action foo() { meta.foo = 1; }
	ActionProfile(128) ap;
	table indirect {
		key = {hdr.ipv4.dst_address: exact;}
		actions = { foo; NoAction; }
		psa_implementation = ap;
	}
	apply {
		indirect.apply();
	}
}

7.12. Селекторы действий

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

 
Рисунок 5. Селекторы действий в PSA.

На рисунке 5 показана таблица с реализацией селектора действий, использующая сопоставление LPM для поля h.f. Второй селектор типа сопоставления служит для задания полей, используемых при поиске спецификации действия в процессе работы.

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

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

Элементы селектора действий могут указывать лишь типы действий, указанные в атрибуте actions реализованной таблицы.

Ниже приведены минимальные требования к селекторам действий в реализации PSA.

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

Дополнительные расширения:

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

Свойство таблицы psa_empty_group_action походе на свойство default_action:

  • оба используют действие в качестве значения;

  • начальное значение задает код P4;

  • при отсутствии свойства psa_empty_group_action в коде P4 используется NoAction();

  • может применяться модификатор const, запрещающий управляющей программе изменять действие;
  • при отсутствии модификатора const управляющая программа может менять действие psa_empty_group_action.

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

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

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

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

extern ActionSelector {
	/// Создание селектора действий с size записей.
	/// @param algo - алгоритм хеширования для выбора члена группы.
	/// @param size - число записей в селекторе действий.
	/// @param outputWidth - размер ключа.
	ActionSelector(PSA_HashAlgorithm_t algo, bit<32> size, bit<32> outputWidth);
	/*
	@ControlPlaneAPI
	{
		entry_handle	add_member		(action_ref, action_data);
		void		delete_member		(entry_handle);
		entry_handle 	modify_member		(entry_handle, action_ref, action_data);
		group_handle 	create_group		();
		void		delete_group		(group_handle);
		void		add_to_group		(group_handle, entry_handle);
		void		delete_from_group	(group_handle, entry_handle);
	}
	*/
}

7.12.1. Пример селектора действий

Блок управления P4 Ctrl в приведенном ниже примере создает экземпляр селектора действий as, который может включать до 128 элементов. Селектор применяет алгоритм crc16 с 10-битовым результатом для выбора элементов в группе.

Таблица indirect_with_selection применяет этот экземпляр путем установки атрибута psa_implementation. Плоскость управления может добавлять элементы и группы в as и каждый элемент может задавать действие foo или NoAction. При программировании записей таблицы плоскость управления не включает поля селектора типа сопоставления в ключ сопоставления. Вместо этого поля селектора типа сопоставления используются для создания списка, передаваемого экземпляру селектора действий. В приведенном ниже примере список {hdr.ipv4.src_address, hdr.ipv4.protocol} передается на вход алгоритма хэширования crc16, используемого для динамического выбора в селекторе действий as.

control Ctrl(inout H hdr, inout M meta) {
	action foo() { meta.foo = 1; }
	ActionSelector(PSA_HashAlgorithm_t.CRC16, 128, 10) as;
	table indirect_with_selection {
		key = {
			hdr.ipv4.dst_address: exact;
			hdr.ipv4.src_address: selector;
			hdr.ipv4.protocol: selector;
		}
	actions = { foo; NoAction; }
	psa_implementation = as;
	}
	apply {
		indirect_with_selection.apply();
	}
}

Управление записями селектора действий при наличии отказов на каналах выходит за рамки PSA. Для быстрого восстановления нужны данные плоскости управления и этот вопрос будет решаться рабочей группой P4 Runtime API16.

7.13. Временные метки

Реализация PSA предоставляет значение ingress_timestamp для каждого пакета, входящего в блок управления Ingress, как поле структуры типа psa_ingress_input_metadata_t. Эта временная метка должна содержать время, близкое к моменту приема устройством первого бита пакета или (вариант) начала синтаксического анализа. Метка не включается автоматически в пакет на входе в блок управления Egress и желающая использовать ingress_timestamp в выходном коде программа P4 должна копировать ее в поле пользовательских метаданных, передаваемое выходному конвейеру. Предоставляется также метка egress_timestamp для каждого пакета, входящего в блок управления Egress, как поле структуры psa_egress_input_metadata_t.

Одним из ожидаемых применений временных меток является их сохранение в экземплярах таблиц или регистров (Register) для реализации контроля протокольных тайм-аутов, где миллисекундной точности достаточно для большинства протоколов. Другим ожидаемым вариантом применения является телеметрия INT17, где требуется точность порядка микросекунд и лучше для измерения задержек в очередях (передача кадра Ethernet jumbo размером 9 Кбайт по каналу 100 Гбит/с занимает 740 нсек).

Для таких приложений рекомендуется обновлять временные метки с периодом не более 1 мксек. Обновление в каждом цикле ASIC или FPGA обеспечивает разумное решение. Скорость обновления временных меток следует делать постоянной. Например, для этого не следует использовать простой подсчет тактов, поскольку тактовая частота устройства может динамически изменяться18.

Временные метки имеют тип Timestamp_t, который является bit<W>, а значение W задает реализация. Предполагается, что значения временных меток будут в течение некоторого времени повторяться в результате переполнения (wrap). Рекомендуется выбирать частоту обновления и число битов так, чтобы повтор значений меток происходил не чаще одного раза в час. Это позволит сделать метки полезными для указанных ниже применений.

  • Контроль тайм-аутов трафика hello и keep-alive, составляющих секунды или минуты.

  • Если временные метки включаются в метаданные без преобразования формата, многим внешним системам анализа данных могут потребоваться преобразования, например, для сравнения временных меток из разных устройств PSA. Этим системам потребуются разные формулы и/или параметры для учета цикличности временных меток или добавление ссылок на внешние источники временных данных. Чем длительней будут циклы временных меток, тем меньше будет объем таких дополнительных работ.

  • Если программа P4 преобразует формат временных меток, ей потребуется доступ к параметрам, которые могут меняться в каждом цикле, например, базовое время, добавляемое к значению метки. Простым способом реализации этого будет обновление плоскостью управления таких данных 1 или 2 раза в течение цикла генерации значений меток (заполнения счетчика).

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

При генерации меток с разрядностью 32 каждую микросекунду продолжительность цикла составит 1,19 часа, а для 42-битовых меток с обновлением каждую наносекунду — 1,22 часа.

От реализации PSA не требуется синхронизация часов, например, по протоколу PTP19 или NTP20.

Фрагмент API плоскости управления, приведенный ниже, может быть частью P4 Runtime API.

// Сообщения TimestampInfo и Timestamp следует добавлять в oneof сообщений Entity.
// Сообщение TimestampInfo предназначено лишь для чтения и попытки изменить его
// не будут иметь эффекта, однако следует сообщать о них (ошибка).
message TimestampInfo {
	// Число битов типа Timestamp_t в устройстве.
	uint32 size_in_bits = 1;
	// Значение временной метки в этом устройстве обновляется
	// increments_per_period раз каждые period_in_seconds секунд.
	uint64 increments_per_period = 2;
	uint64 period_in_seconds = 3;
}
// Значение timestamp доступно для чтения и записи. Отметим, что при наличии
// значений меток в экземплярах таблиц или регистров они не будут обновляться
// в результате записи значения timestamp для устройства, которое 
// предназначено лишь для для инициализации и тестирования.
message Timestamp {
	bytes value = 1;
}

Для каждого пакета P, обрабатываемого входным и выходным конвейером с минимальной задержкой в буфере пакетов, гарантируется, что значение egress_timestamp будет таким же или чуть больше значения ingress_timestamp, заданного для пакета на входе. «Почти» в данном случае означает, что разность (egress_timestamp — ingress_timestamp) должна быть достаточно точной оценкой задержки в буфере пакетов, возможно нулевой, если эта задержка будет меньше интервала обновления меток.

Рассмотрим два пакета, которым одновременно (например, в один машинный такт) назначены временные метки — одному ingress_timestamp в начале входной обработки, другому — egress_timestamp в начале выходной. Эти метки могут отличаться на несколько десятков наносекунд (или один «тик» временных меток, если он больше) по причине практических сложностей синхронизации.

Напомним, что двоичные операции + и — для типа bit<W> в P4 определены с использованием арифметики без знака с учетом перехода через максимум (wrap-around). Таким образом, даже при переходе временных меток через максимум всегда можно вычислить разность между метками t1 и t2, используя выражение t2−t1 (если интервал превышает 2W «тиков», это будет псевдонимом результата). Например, если для меток применяется W >= 4 битов и t1 = 2W − 5, а t2 = 3, то t2 − t1 = 8. Таким образом не возникает потребности в условных операциях для вычисления интервала.

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

Другим примером являются приложения, которым требуется высокая точность с учетом всех битов временных меток, но плоскость управления и программа P4 проверяют все записи массива регистров, где хранятся эти временные метки, не чаще 1 раза в 5 секунд для предотвращения перехода через максимум. В этом случае программа P4 может отбросить старшие биты временных меток, чтобы оставшиеся биты переходили через максимум каждые 8 секунд и сохранять эти усеченные метки в экземпляре Register.

7.14. Дайджест пакета

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

Дайджест может включать любые данные плоскости управления. Поскольку программа P4 может иметь множество экземпляров Digest, каждый из которых передает свое содержимое, реализации PSA нужно различать дайджесты, созданные разными экземплярами. В PSA дайджест создается вызовом метода pack для экземпляра Digest. Аргументом вызова является включаемое в дайджест содержимое, которое часто является набором значений в структуре P4. Компилятор выбирает подходящий формат последовательной передачи содержимого дайджеста локальному программному агенту, который отвечает за доставку дайджеста в заданном спецификацией P4 Runtime API формате.

Программа PSA может создать множество экземпляров Digest в одном блоке управления IngressDeparser и применять не более одного вызова pack для каждого экземпляра в процессе работы этого блока управления. От реализации PSA не требуется поддержка применения внешнего блока Digest в блоке управления EgressDeparser.

При создании множества экземпляров Digest в процессе обработки одного пакета не требуется упаковка всех дайджестов в одно сообщение. Реализация может, например, помещать их отдельные очереди для каждого экземпляра Digest, а затем доставлять их контроллеру в желаемом порядке. Реализации PSA рекомендуется передавать плоскости управления сообщения Digest от одного экземпляра Digest в порядке их создания.

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

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

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

Ниже представлен фрагмент примера, демонстрирующего применение дайджеста для уведомления плоскости управления для о новой комбинации Ethernet MAC и входного порта в принятом пакете.

struct mac_learn_digest_t {
	EthernetAddress srcAddr;
	PortId_t	ingress_port;
}
struct metadata {
	bool			send_mac_learn_msg;
	mac_learn_digest_t	mac_learn_msg;
}

// Это часть функциональности обычного моста Ethernet с обучением.
// Плоскость управления обычно задает одинаковые ключи для таблиц 
// learned_sources и l2_tbl. Записи в l2_tbl служат для поиска по 
// MAC-адресу получателя и совпадение дает выходной порт для пакета.
// Записи в learned_sources такие же, но действием для них служит
// NoAction. При отсутствии адреса в learned_sources разумно передать
// плоскости управления сообщение с MAC-адресом отправителя и номером
// принявшего пакет порта. Плоскость управления примет решение о 
// добавлении записи с MAC-адресом отправителя в обе таблицы и l2_tbl 
// далее будет передавать пакеты в ingress_port этого пакета.
// Это лишь простой пример, который не включает, например, лавинной
// рассылки, которую мост с обучением обычно применяет при отсутствии 
// в таблице MAC-адреса получателя.
control ingress(inout headers hdr,
		 inout metadata meta,
		 in psa_ingress_input_metadata_t istd,
		 inout psa_ingress_output_metadata_t ostd)
{
	action unknown_source () {
		meta.send_mac_learn_msg = true;
		meta.mac_learn_msg.srcAddr = hdr.ethernet.srcAddr;
		meta.mac_learn_msg.ingress_port = istd.ingress_port;
		// meta.mac_learn_msg передается плоскости управления
		// в блоке управления IngressDeparser.
	}
	table learned_sources {
		key = { hdr.ethernet.srcAddr : exact; }
		actions = { NoAction; unknown_source; }
		default_action = unknown_source();
	}
	action do_L2_forward (PortId_t egress_port) {
		send_to_port(ostd, egress_port);
	}
	table l2_tbl {
		key = { hdr.ethernet.dstAddr : exact; }
		actions = { do_L2_forward; NoAction; }
		default_action = NoAction();
	}
	apply {
		meta.send_mac_learn_msg = false;
		learned_sources.apply();
		l2_tbl.apply();
	}
}
control IngressDeparserImpl(packet_out packet,
			      out empty_metadata_t clone_i2e_meta,
			      out empty_metadata_t resubmit_meta,
		  	      out empty_metadata_t normal_meta,
			      inout headers hdr,
			      in metadata meta,
			      in psa_ingress_output_metadata_t istd)
{
	CommonDeparserImpl() common_deparser;
	Digest<mac_learn_digest_t>() mac_learn_digest;
	apply {
		if (meta.send_mac_learn_msg) {
			mac_learn_digest.pack(meta.mac_learn_msg);
		}
		common_deparser.apply(packet, hdr);
	}
}

8. Неделимость операций API плоскости управления

Все операции добавления, удаления и изменения таблиц должны быть неделимыми (atomic) относительно пересылки пакета. Т. е. для каждой табличной операции apply и каждой операции плоскости управления по добавлению, удалению или изменению записи в таблице операция apply должна выполняться так, будто изменения таблицы еще не началось, либо оно уже завершилось. Программе P4 никогда не следует вести себя так, будто операция плоскости управления выполнена частично.

Отметим, что это требование применяется индивидуально к каждой табличной операции apply. От реализации PSA не требуется поддержка выполнения множества операций apply для одной таблицы в одном вызове блока управления. Если реализация поддерживает это, плоскости управления разрешается выполнить обновление между двумя операциями apply для одного пакета.

Реализации PSA следует выдавать ошибку или отказ при компиляции программ P4, для которых она не может выполнить требования неделимости. Например, реализация может выполнять эти требования с действиями, включающими не более 128 битов параметров, тогда ей следует выдавать ошибку при попытке компиляции программы P4, содержащей действия с большим объемом параметров.

Предположим, например, что таблица T имеет действие A со 100 битами (суммарно) параметров, а плоскость управления добавила в таблицу запись с ключом поиска K и действием A. Позднее плоскость управления обновила запись для ключа K, не меняя ключ, но изменив 100 битов параметров действия. Для каждого пакета, вызывающего apply для таблицы T и записи с ключом K, нужно выполнить действие A со старыми или новыми 100 битами параметров.

P4 Runtime API позволяет контроллерам группировать сообщения для выполнения нескольких операций в один прием, как описано здесь. В этом случае реализации PSA нужно лишь обеспечит неделимость каждой отдельной операции. Для последовательности операций добавления, удаления или обновления неделимость не требуется.

То же самое применимо ко всем операциям API плоскости управления с внешними блоками (extern), если в документации явно не указано иное. В частности, одиночным операциям ActionProfile и ActionSelector, таким как добавление в группу или удаление из нее, добавление или удаление пустой группы и изменение параметров действия, добавленного ранее в группу, должна обеспечиваться неделимость. Неделимыми также должны быть операции плоскости управления по чтению и записи отдельных элементов массива Register, кроме того они должны происходить до или после (но не в процессе) любого блока кода P4, помеченного аннотацией @atomic. В плоскости управления нет операций над Register, которые могут неделимо читать элемент, а затем записывать в него измененное значение.

Если нужно из программы P4 неделимо считать, изменить и записать обратно элемент массива Register, следует сделать так, чтобы операции чтения, изменения и записи в программе P4 выполнялись «пакетом», который плоскость управления может внедрить в плоскость данных (например, через операции packet in и packet в P4 Runtime API).

Высокоскоростная реализация PSA может обрабатывать сотни или тысячи пакетов между отдельными операциями плоскости управления. Имеются базовые методы «записи в таблицу из будущего в прошлое потока данных» (write tables from later to earlier in the data flow), которые иногда называют back to front или pointer flipping, используемые плоскостью управления для достижения эффекта, подобного обеспечению неделимости операций над последовательностью таблиц в процессе пересылки пакета. Имеются исследования таких методов в более общем контексте21.

A. Нерешенные вопросы

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

A.1. Селекторы действий

Параметр size в экземпляре action_selector определяет максимальное число элементов селектора. В некоторых случаях может оказаться полезным позволить контроллеру динамически выделять ресурсы для селектора или применять селекторы разных размеров на разных платформах, используя одну программу P4.

Нужно также формализовать взаимодействие профилей и селекторов действий со счетчиками и измерителями.

A.2. Обнаружение и контроль перегрузок

В текущей реализации PSA нет механизмов, позволяющих заметить приближение заполнения буферов пакетов отдельного выходного порта или очереди. Невозможно реализовать это без использования внешних по отношению к PSA механизмов, подобных явному уведомлению о насыщении — ECN22. Можно задать небольшое поле (скажем, 1 бит) в метаданных, связываемое с каждым пакетом в начале выходной обработки, которое будет указывать для пакета перегрузку буферов.

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

Желательно задать в PSA небольшой набор полей, которые будут служить входными данными для множества алгоритмов контроля перегрузок. Одним из вариантов является использование хэшированного идентификатора потока, часто реализуемого в форме хэш-функции от полей заголовка IP, таких как адреса получателя и отправителя, протокол IP и, возможно порты TCP/UDP для отправителя и получателя. С учетом того, что программируемые на P4 устройства могут обрабатывать не только пакеты IP, желательно найти механизм более общего назначения для устройств PSA.

A.3. Возможность полной реализации внутриполосной телеметрии

Одним из многообещающих применений программируемых на P4 сетевых устройств является внутриполосная телеметрия INT24. Хотя в PSA уже реализованы такие важные для телеметрии механизмы, как временные метки, еще не разработаны механизмы доступа к информации о загрузке каналов на выходных портах и заполнении очередей25.

A.4. Профили PSA

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

B. Реализация внешнего блока InternetChecksum

В RFC 1071 и RFC 1141 описан эффективный расчет контрольных сумм Internet, особенно для программных реализаций. Ниже приведены прототипы реализации методов для внешнего блока InternetChecksum с использованием синтаксиса и семантики P416, расширения цикла for и оператора return, возвращающего значение функции. Для экземпляра объекта InternetChecksum требуется как минимум внутреннее состояние в форме 16-битового вектора (sum в приведенном примере).

// Один из способов получить дополнение до 1 суммы двух
// 16-битовых значения.
bit<16> ones_complement_sum(in bit<16> x, in bit<16> y) {
	bit<17> ret = (bit<17>) x + (bit<17>) y;
	if (ret[16:16] == 1) {
		ret = ret + 1;
	}
	return ret[15:0];
}
bit<16> sum;
void clear() {
	sum = 0;
}
// Размер data должен быть кратным 16 битам
void add<T>(in T data) {
	bit<16> d;
	for (каждой 16-битовой части d из data) {
		sum = ones_complement_sum(sum, d);
	}
}
// Размер data должен быть кратным 16 битам
void subtract<T>(in T data) {
	bit<16> d;
	for (каждой 16-битовой части d из data) {
		// ~d - отрицание d в арифметике с дополнением до 1.
		sum = ones_complement_sum(sum, ~d);
	}
}
// Контрольная сумма Internet является дополнением до 1 суммы 
// дополнений до 1 соответствующих частей пакета. Указанные выше
// методы возвращают сумму дополнений до 1 в переменной sum.
// get() возвращает побитовой отрицание sum, которое является
// дополнением sum до 1.
bit<16> get() {
	return ~sum;
}
bit<16> get_state() {
	return sum;
}
void set_state(bit<16> checksum_state) {
	sum = checksum_state;
}

C. Пример реализации внешнего блока Counter

Приведенный ниже пример, в частности, функция next_counter_value служит лишь для иллюстрации и не требует его реализации в PSA. Формат хранения PACKETS_AND_BYTES также служит лишь примером. Реализации могут хранить состояние иначе, если API плоскости управления возвращает корректные значения счетчиков пакетов и байтов.

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

  • кольцевые (wrap around) счетчики;
  • счетчики с насыщением, «застывающие» при максимальном значении без сброса в 0.

Спецификация не задает использование того или иного подхода в плоскости данных. Реализациям следует стремиться к предотвращению потери данных в счетчиках. Базовым методом реализации является использование неделимых операций чтения и сброса счетчиков в плоскости данных, которые может вызывать программа плоскости управления. Операции вызываются плоскостью управления достаточно часто для предотвращения перехода счетчиков через 0 или насыщения, а считанные значения прибавляются к хранящимся в памяти драйвера значениям большего размера.

Counter(bit<32> n_counters, PSA_CounterType_t type) {
	this.num_counters = n_counters;
	this.counter_vals = новый массив из n_counters элементов размера W;
	this.type = type;
	if (this.type == PSA_CounterType_t.PACKETS_AND_BYTES) {
		// Подсчет пакетов и байтов использует общее хранилище состояния.
		// Нужны ли отдельные конструкторы с дополнительным аргументом,
		// задающим размер счетчика байтов?
		W shift_amount = TBD;
		this.shifted_packet_count = ((W) 1) << shift_amount;
		this.packet_count_mask = (~((W) 0)) << shift_amount;
		this.byte_count_mask = ~this.packet_count_mask;
	}
}
W next_counter_value(Wcur_value, PSA_CounterType_t type) {
	if (type == PSA_CounterType_t.PACKETS) {
		return (cur_value + 1);
	}
	// Учитываемые в packet_len байты зависят от реализации.
	PacketLength_t packet_len = <размер пакета в байтах>;
	if (type == PSA_CounterType_t.BYTES) {
		return (cur_value + packet_len);
	}
	// Требуется тип PSA_CounterType_t.PACKETS_AND_BYTES.
	// В счетчике размера W младшие байты служат счетчиком байтов,
	// а старшие учитывают пакеты.
	// Это просто один из форматов хранения и реализация может иначе
	// хранить состояние packets_and_byte, если API плоскости данных
	// корректно возвращает значения счетчиков байтов и пакетов.
	W next_packet_count = ((cur_value + this.shifted_packet_count) &
				  this.packet_count_mask);
	W next_byte_count = (cur_value + packet_len) & this.byte_count_mask;
	return (next_packet_count | next_byte_count);
}
void count(in S index) {
	if (index < this.num_counters) {
		this.counter_vals[index] = next_counter_value(this.counter_vals[index], this.type);
	} else {
		// Значение counter_vals не обновляется при выходе индекса за
		// границы диапазона. Запись отладочных данных описана ниже.
	}
}

При выходе индекса за границы диапазона можно записывать дополнительную отладочную информацию:

  • число фактов выхода индекса за границы;

  • FIFO для первых N выходов индекса за границу (N определяется реализацией, например, 1);

  • рекомендуется также сохранять информацию о точке вызова в программе P4 метода count() с выходящим за границы индексом.

D. Обоснование архитектуры

D.1. Зачем нужна выходная обработка?

В чем польза от разделения обработки пакетов в коммутаторе на входную и выходную?

Имеются микросхемы ASIC, которые по сути выполняют лишь входную обработку, затем отправляют пакет в буфер с одной или несколькими очередями, откуда он передается наружу без (или почти) выходной обработки. В таких устройствах возникают сложности с выполнением некоторых задач.

1. Изменение пакетов в последний момент

Если нужно измерить задержку в устройстве и поместить результат измерения в пакет, обычно нет возможности узнать задержку в очереди до отправки пакета в буфер. В некоторых особых случаях задержку можно предсказать (например, при использовании одной очереди FIFO с постоянной скоростью выходного порта при отсутствии кадров, подобных Ethernet pause).

Но для каналов с переменной скоростью, например, Ethernet с управлением потоком данных с помощью кадров pause, в Wi-Fi при изменении качества сигнала или при наличии очередей по классам обслуживания со взвешенным планированием, невозможно предсказать в момент отправки пакета в очередь время его выхода из очереди. Задержка в очереди зависит от неизвестных событий в будущем, таких как получение кадров Ethernet или число и размер пакетов, поступающих в очереди с разным классом обслуживания.

В таких случаях наличие выходной обработки позволяет выполнить требуемые измерения, после чего легко рассчитать время пребывания пакета в очереди как разность dequeue time — enqueue time, что позволяет дополнительно изменить пакет.

2. Эффективность и гибкость групповой обработки

В устройстве PSA можно обрабатывать групповой трафик, выполняя операции рециркуляции и клонирования для каждой из N создаваемых копий, но это снижает возможности обработки вновшь прибывающих пакетов во входном конвейере, а эти пакеты могут оказаться более важными по сравнению с обрабатываемыми групповыми пакетами.

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

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

При такой организации multicast-обработки все еще сохраняется потребность в различной обработке разных копий одного пакета. Например, в копию для выходного порта 5 нужно поместить тег VLAN 7, а для порта 2 — VLAN 18. Аналогичная задача возникает при отправке групповых пакетов в туннели VXLAN, GRE и т. п. За счет изменения выходной обработки на уровне пакета логика репликации остается достаточно простой — создаются идентичные копии по завершении входного конвейера, различающиеся лишь неким уникальным идентификатором, позволяющим различать эти копии при выходной обработке.

D.2. Неизменность выходного порта в процессе выходной обработки

Почему в программе P4 нельзя сменить выходной порт в процессе выходной обработки?

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

Разработаны алгоритмы планирования отправки пакетов, такие как беспристрастные взвешенные очереди, которые помогаю определить и установить набор из множества очередей FIFO для последовательного считывания из них пакетов и отправки в порт. Эти алгоритмы планирования работают в реальном масштабе времени с очень жесткими характеристиками. Если они слишком медленны, выходной порт будет частично простаивать. Если алгоритм слишком быстр, мы возвращаемся к упомянутой раньше проблеме считывания пакетов из буфера со скоростью, превышающей скорость передачи порта и приходится снова буферизовать ракеты или отбрасывать их.

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

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

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

D.3. Входной сборщик и выходной анализатор

В P414 нет входного сборщика и выходного анализатора. Зачем они в PSA?

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

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

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

Ситуация с выходным сборщиком похожа. Делая его явным и отдельным блоком, вы получаете полный контроль над данными, включаемыми в пакет при отправке его в буфер. В P414 неявно предполагается, что все метаданные пакета, применяемые в выходном коде, передаются вместе с пакетом. Это можно сделать и в программах P416 для архитектуры PSA, но сейчас это должно быть явным. В PSA можно ограничить объем передаваемых с пакетом метаданных, что может быть важно для блоков ввода-вывода буфера пакетов.

E. Устройства PSA с несколькими конвейерами

В современных высокоскоростных сетевых устройствах применяются ASIC с тактовой частотой 1 — 2 ГГц. В приведенном ниже обсуждении предполагается частота 1 ГГц, но все рассмотренные аспекты линейно масштабируются по частоте.

Обычно часть сетевого ASIC проектируется так, чтобы обработка нового пакета начиналась один раз в каждом такте и заканчивалась в каждом такте. Задержка от начала до завершения обработки может составлять сотни циклов тактирования. Таблицы P4 в таких ASIC обычно размещаются в памяти TCAM и SRAM. TCAM позволяет выполнять 1 поиск за такт, а SRAM — 1 операцию чтения или записи. Хотя имеются многопортовые системы SRAM, позволяющие выполнять несколько операций чтения и/или записи за один такт, они существенно проигрывают по размеру и потребляемой мощности по сравнению с однопортовыми. При создании многопортовых систем TCAM рост размеров и потребляемой мощности будет еще больше, чем для многопортовых SRAM. Типовым способом повышения скорости поиска в TCAM является параллельная работа, когда создается несколько копий TCAM, что обеспечивает линейный рост размеров и потребляемой мощности.

По этом причинам для создания коммутационных ASIC, обрабатывающих пакеты в N быстрее тактовой частоты (например, 2 миллиарда пакетов в секунду при частоте 1 ГГц), наиболее простым решением является параллельная обработка пакетов26 с созданием N конвейеров27, каждый из которых обрабатывает 1 миллиард пакетов в секунду. Созданные на основе такого подхода устройства PSA обычно будут включать N входных конвейеров и N выходных. Обычно к каждому конвейеру жестко привязывается множество физических портов ASIC, например, в устройстве с 32 портами 100G Ethernet можно привязать порты 0 — 15 к конвейеру 0, а порты 16 — 31 к конвейеру1. Все пакеты, принятые портами 0 — 15, обрабатываются входным конвейером 0, затем передаются в буфер пакетов (если не отброшены на входе),а после этого — в выходной конвейер 0, если они направлены в выходные порты 0 — 15, или в выходной конвейер 1, если направлены в порты 16 — 31. В таком устройстве обычно требуется применять одну и ту же программу P4 в каждом из N входных и выходных конвейеров.

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

Независимо от описанного выше подхода, применение таблиц в P4 выполняется в параллельном режиме, поскольку конвейеры могут работать совершенно независимо один от другого без обмена информацией между собой (имеющееся исключение описано ниже). То же относится к большинству внешних блоков PSA, например, ActionProfile, ActionSelector, Checksum, Digest, Hash, Random. Общим у таблиц P4 и этих внешних элементов является то, что программы P4 либо совсем не могут менять их состояние (например, таблицы, ActionProfile, ActionSelector), либо могут могут менять его лишь так, что это не влияет на обработку других пакетов (например, Checksum, Digest, Hash). Блок Random является особым случаем — обновление состояния генератора псевдослучайных чисел может влиять на обработку других пакетов, но обычно это связано со способом применения таких чисел (например, случайный выбор пакета для маркировки или отбрасывания в алгоритме RED).

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

Рассмотрим устройство с поддержкой независимых измерителей в каждом конвейере. Если нужно учесть все пакеты, соответствующие записи таблицы X, но не более Y байт/сек, можно выполнить координацию состояний между конвейерами, например, с помощью протоколов когерентности кэша, обычно реализованных с многоядерных CPU, или рециркуляции пакетов в общий конвейер, где сохраняется состояние измерителя. Оба варианта имеют низкую производительность (по крайней мере в части случаев) и сложны в реализации. Коммутаторы обычно поддерживают независимые состояния измерителей в каждом конвейере, не координируя их. Эта проблема не специфична для коммутаторов и относится к категории доступа к изменяемому состоянию распределенной системы, что связано не только с вопросами точности, но и с проблемой производительности.

Внешний блок Register имеет более общее назначение по сравнению с регистрами и для него характерны те же проблемы разделения состояний между множеством конвейеров. Рекомендуется обсудить эти вопросы с производителем устройства PSA, если предполагается их влияние на работу программы P4. Если устройство PSA не согласует состояния автоматически, следует применять общую стратегию, представленную выше для измерителей — воспринимать независимое поведение регистров каждого конвейера и рециркулировать нужные пакеты в конвейер, поддерживающий общее состояние.

Отметим, что предложенное свойство таблицы psa_idle_timeout обеспечивает способ использования операций apply для обновления состояний таблиц P4. Для каждой записи таблицы требуется по меньшей мере 1 бит для представления момента последнего совпадения с записью и это значение обновляется при каждой операции apply. Если это состояние в таблице с данной опцией не согласуется автоматически между конвейерами, значения в разных таблицах могут различаться. Запись с ключом X в одном конвейере может оставаться неиспользуемой дольше заданного тайм-аута, тогда как в других конвейерах она может применяться чаще. Одним из возможных решений этой проблемы для устройства PSA и зависящей от реализации плоскости управления является явное указание плоскости управления наличия множества конвейеров, например, путем назначения каждому конвейеру, таблице и внешнему блоку своего имени.

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

F. Упорядочение пакетов

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

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

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

Рекомендация 3. Индивидуальные пакеты PRE (т. е. те, которые идут по пути «поместить в очередь» в псевдокоде параграфа 6.2. Поведение пакетов по завершении входной обработки), принятые из одного порта, прошедшие входной конвейера однократно (без рециркуляции и повторного представления), переданные а PRE с одним значением класса обслуживания (class_of_service) и адресованные в один выходной порт, следует обрабатывать с том же порядке, в котором они пришли на входную обработку.

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

Если реализация следует рекомендациям 1 — 3, индивидуальный трафик с одним классом обслуживания будет сохранять относительный порядок пакетов при прохождении через устройство.

Рекомендация 4. Рассмотрим групповые пакеты PRE (пакеты, следующие по пути «создается клон» в псевдокоде параграфа 6.2. Поведение пакетов по завершении входной обработки), приходящие через один входной порт, проходящие входную обработку однократно и передаваемые в PRE с одинаковыми парами (class_of_service, multicast_group). Копии одного исходного пакета, адресованные в один выходной порт и имеющие одинаковые пары (egress_port, instance), следует обрабатывать на выходе в порядке их входной обработки.

Групповые пакеты с разными значениями class_of_service не рассматриваются по причине наличия в PRE раздельных очередей для разных классов обслуживания.

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

Если реализация следует рекомендациям 1, 2 и 4, групповой трафик с одним классом обслуживания будет сохранять относительный порядок при прохождении через устройство в условиях достаточно долгой неизменности членов групп.

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

Ниже приведены некоторые основания для рекомендаций этого приложения.

  1. Ожидания хостов.

    Хотя в протоколе IP нет строгих требований в части порядка пакетов, передаваемых от одного хоста к другому, имеются широко распространенные реализации TCP, для которых производительность работы в реальном масштабе времени значительно снижается при нарушении порядка доставки пакетов в сети. Для решения этой проблемы было выполнено множество исследований и разработок (например, современные реализации Linux TCP, начиная с 2011 г., когда было выпущено ядро 2.6.35, значительно устойчивей своих предшественников), однако остается еще много реализаций TCP, страдающих от нарушения порядка. Примеры можно найти в работке Kandula и соавторов28, где проведено исследование повышения устойчивости TCP к нарушениям порядка доставки.

    Такие реализации TCP считают подтверждения с повторяющимися кумулятивными порядковыми номерами вероятным указанием на потерю пакетов в сети и сокращают окно передачи для предотвращения перегрузок в сети.

    Хотя приложениям UDP также нужно быть готовыми к возможному нарушению порядка пакетов в сети, некоторые из них ведут себя некорректно при таком нарушении29.

    Упомянутые выше причины служат основанием для использования хэш-значений полей заголовков пакетов (таких как IP-адреса отправителя и получателя, номера портов TCP или UDP) при выборе между равноценными путями ECMP30 и каналами LAG. Такой выбор между параллельными путями помогает сохранить порядок пакетов за счет снижения равномерности распределения нагрузки. Если бы внутренняя реализация сетевого устройства меняла порядок пакетов, это стало бы еще одной причиной его нарушения.

  2. Реализация протоколов с состояниями.

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

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

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

G. Поддержка пустых групп в селекторах действий

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

Некоторые пользователи P4 выразили заинтересованность в предоставлении возможности клиенту P4Runtime (контроллер) удалять последний элемент группы селектора действий и получать в результате предсказуемое поведение плоскости данных.

Например, если имеется таблица, отображающая идентификаторы логических интерфейсов на номера физических портов, которая использует селектор действий для реализации LAG, что следует делать контроллеру, когда в LAG активен лишь один физический порт, а остальные отключены (down)? С точки зрения контроллера желаемым поведением будет ввод команды P4Runtime для удаления последнего элемента в группе и выполнение действия для пустой группы по отбрасыванию пакета, для всех пакетов, применяя таблицу и выбирая пустую группу.

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

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

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

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

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

Это поведение можно реализовать с помощью дополнительной логики в сервере P4Runtime (иногда называемом агентом). Идея состоит в том, что агент получает пустое действие группы со значениями параметров действий, например, из скомпилированного представления программы P4.

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

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

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

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

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

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

Реализация PSA с агентом, поддерживающим пустые группы селекторов действий описанным способом, должна выполнять указанные выше пары шагов неделимо, как описано в разделе 8. Неделимость операций API плоскости управления, но допускается обработка одного или двух пакетов между двумя этапами.

Если реализация PSA поддерживает одновременно множество разных имен действий внутри группы, приведенные ниже сведения можно пропустить. Там описано лишь поведение для плоскости данных, которая разрешает включать в группу лишь одноименные действия.

Поскольку в реализации PSA не требуется поддерживать одновременно разные имена внутри группы селектора действий (7.12. Селекторы действий), в программе для обеспечения переносимости может потребоваться изменение одного или нескольких действий используемых в таблицах с селекторами.

Например, в отмеченном ранее варианте выбора порта LAG тиеется лишь одно действие для таблицы lag, как показано ниже.

action set_output_port (PortId_t p) {
	user_meta.out_port = p;
}
ActionProfile(128) ap;
table lag {
	key = {
		// ... поля ключа ...
	}
	actions = { set_output_port; }
	psa_implementation = ap;
}
control cIngress (inout headers hdr,
		   inout metadata user_meta,
		   in psa_ingress_input_metadata_t istd_meta,
		   inout psa_ingress_output_metadata_t ostd_meta)
{
	apply {
		// ... предшествующий код входного конвейера ...
		lag.apply();
		send_to_port(ostd_meta, user_meta.out_port);
		// ... последующий код входного конвейера ...
	}
}

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

Подход 1. Использование недействительного номера порта.

Выбирается значение PortId_t, которое не соответствует ни одному физическому порту устройства31, и применяется для пустого действия в пустой группе. В примере кода после lag.apply добавлен оператор if для проверки этого значения.

apply {
	// ... предшествующий код входного конвейера ...
	lag.apply();
	if (user_meta.out_port == PORT_INVALID_VALUE) {
		ingress_drop(ostd_meta);
	} else {
		send_to_port(ostd_meta, user_meta.out_port);
	}
	// ... последующий код входного конвейера ...
}

Подход 2. Добавление дополнительных параметров действия.

В этом случае добавляется 1-битовый параметр, указывающий отбрасывание пакета. Сохраняется необходимость использования оператора if после применения таблицы (apply).

action set_output_port (PortId_t p, bit<1> drop) {
	user_meta.out_port = p;
	user_meta.drop = drop;
}
// ...
	apply {
		// ... предшествующий код входного конвейера ...
		lag.apply();
		if (user_meta.drop == 1) {
			ingress_drop(ostd_meta);
		} else {
			send_to_port(ostd_meta, user_meta.out_port);
		}
	// ... последующий код входного конвейера ...
	}

В любом случае реализация может также поддерживать применение оператора if внутри действия set_output_port, не PSA не требует такой поддержки.

H. История выпусков

 

Выпуск

Дата

Описание изменений

1.0

1 марта 2018 г.

Исходный выпуск

1.1

22 ноября 2018 г.

Версия 1.1, см. ниже.

 

H.1. Изменения в версии 1.1

H.1.1. Численные преобразования между P4Runtime API и плоскостью данных

После выпуска PSA v1.0 было проведено несколько встреч рабочей группы по вопросам численных преобразования между значениями PortId_t (а также ClassOfService_t и возможно других значений в будущем). В PSA v1.1 отражены принятые решения.

  • 4.1. Определения типов PSA;

  • 4.4. Представление данных в плоскости управления и данных.

H.1.2. Возможность создания множества копий в сеансе клонирования

В PSA v1.0 запросы на клонирование ограничены созданием одной копии, передаваемой в выходной порт. PSA v1.1 позволяет настроить сеанс клонирования путем задания пар (egress_port, instance), подобно настройке multicast-групп.

  • 6.2. Поведение пакетов по завершении входной обработки;

  • 6.4.5. Групповая адресация и клоны;

  • 6.5. Поведение пакетов по завершении выходной обработки;

  • 6.8. Клонирование пакетов.

H.1.3. Добавлено свойство таблицы psa_idle_timeout

Добавление этого свойства в PSA v1.1 согласовано с его поддержкой в P4Runtime API. Использование этого свойства помогает разработчикам P4 указать, что таблица должна поддерживать состояние, указывающее время последнего совпадения для каждой записи, а в случае отсутствия совпадений в течение установленного плоскостью управления периода, передавать контроллеру уведомление.

  • 7.2.1. Уведомление о тайм-ауте для записи таблицы.

H.1.4. Добавлено свойство таблицы psa_empty_group_action

PSA v1.0 не задает поведения таблиц с реализацией ActionSelector для случаев, когда пакет соответствует записи, настроенной с пустой группой селектора действий. PSA v1.1 рекомендует (но не требует) от таких реализаций поддержки нового свойства таблиц psa_empty_group_action, значение которого указывает действие, выполняемое в таких ситуациях.

  • 7.12. Селекторы действий.

H.1.5. Прочие изменения

В PSA v1.0 поддержка внешнего блока Digest требовалась в блоках управления IngressDeparser и EgressDeparser. Сейчас она не требуется для блока EgressDeparser.

  • В таблице 5 указаны блоки управления, которые могут создавать и вызывать экземпляры extern.

H.1.6. Изменения в файле psa.p4

  • Изменения в соответствии с численными преобразованиями P4Runtime API для типов PortId_t и ClassOfService_t.

  • Исключен устаревший внешний блок ValueSet, поскольку конструкция value_set была добавлена в спецификацию P416 версии 1.1.0.

  • Исправлены несколько опечаток в комментариях к API плоскости управления.

  • Исключен макрос #define PSA_SWITCH с аргументами, поскольку спецификация P416 не требует от препроцессора P416 поддержки таких макросов.

H.1.7. Изменения в примерах программ PSA из каталога p4-16/psa/examples

  • Небольшие изменения для приведения в соответствие с последними изменения в численном преобразовании для типа PortId_t в P4Runtime API.

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

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

nmalykh@protocols.ru

1Portable Switch Architecture.

2Packet buffer and Replication Engine — машина буферизации и репликации пакетов.

3Buffer Queuing Engine — машина очередей.

4Предполагается, что включаемые файлы psa.p4 для разных платформ будут в основном похожи один на другой. Кроме различий в размерах для типов PSA ожидаются различия в аннотациях блоков extern и п. п., позволяющие компилятору P4 для платформы выполнить свою работу.

5P4 Runtime API определяется как файл Google Protocol Buffer (protobuf) .proto, описание которого доступно по ссылке https://github.com/p4lang/p4runtime.

6P4 Runtime API определяется как файл Google Protocol Buffer .proto, описание которого доступно по ссылке https://github.com/p4lang/p4runtime.

7Хотя 10 Мбит не кажется большим объемом для компьютеров с памятью в сотни Гбайт, скоростные реализации PSA являются ASIC, где таблицы хранятся во встроенной памяти (как кэш-память обычных CPU). Intel i9-7980XE (2017 г.) имеет кэш-память L3 198 Мбит, используемую ядрами CPU. В процессорах Intel Core седьмого поколения, выпущенных в 2017 г, с памятью не меньше 100 Мбит L3-кэша стоимость составляет около $9/Мбит (https://en.wikipedia.org/wiki/List_of_Intel_microprocessors).

8С открытом компиляторе P4 p4c планируется поддержка опции для включения численных преобразований дополнительных типов без изменения программ P4 или включаемого файла psa.p4. Эти типы будут указываться по именам.

9TBD: Для значений типа Timestamp_t рассматриваются численные преобразования в программе агента между зависимым от платформы значением и значением общего блока, а также значением 0 для всех платформ.

10TBD: Неясно, всегда ли минимальный размер данных составляет 46 байтов (64 байта минимального кадра Ethernet за вычетом 14 байтов заголовка 14 и 4 байтов CRC) или реализация может не включать некоторые байты.

11Random Early Detection — упреждающее отбрасывание случайного пакета.

12Approximate Fair Dropping — сравнительно беспристрастное отбрасывание.

13Link Aggregation Group — группа агрегирования каналов.

14Priority code point — код приоритета.

15Differentiated service code point — код дифференцированного обслуживания

16P4 Runtime API определяется как файл Google Protocol Buffer (protobuf) .proto, описание которого доступно по ссылке https://github.com/p4lang/p4runtime.

17In-band Network Telemetry — телеметрия по основному каналу связи, http://p4.org/p4/inband-network-telemetry

21Pavol Cerny, Nate Foster, Nilesh Jagnik, and Jedidiah McClurg, «Consistent Network Updates in Polynomial Time». International Symposium on Distributed Computing (DISC), Paris, France, September 2016.

23Approximate Fair Drop.

24In-band Network Telemetry, http://p4.org/p4/inband-network-telemetry

27Здесь и в спецификации P4 термин конвейер (pipeline) относится к части реализации P4, обеспечивающей, например, поведение блоков IngressParser, Ingress, затем IngressDeparser в PSA. Это принято в P4, хотя следует отметить наличие других аппаратных решений, которые реализуют функции конвейера иначе, например, в наборе параллельно работающих ядер CPU, каждое из которых обрабатывает свой пакет.

28S.Kandula, D. Katabi, S. Sinha, and A. Berger, «Dynamic load balancing without packet reordering», ACM SIGCOMM Computer Communication Review, Vol. 37, No. 2, April 2007,

30Equal Cost Multi Path.

31TBD. Возможно для такого порта следует определить имя, но в настоящее время PSA не включает такого определения.

Рубрика: SDN, Сетевое программирование | Комментарии к записи Архитектура переносимых коммутаторов P4_16 (проект) отключены

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

image_print

PDF

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

typedef

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

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

header

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

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

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

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

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

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

struct

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Действия

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

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

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

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

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

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

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

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

Таблицы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Счетчики

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

Регистры

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

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

nmalykh@protocols.ru

Рубрика: Сетевое программирование | Комментарии к записи Памятка по языку P4 отключены

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

image_print

PDF

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

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

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

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

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

behavioral-model

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

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

education

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

governance

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

grpc (ветвь grpc/grpc)

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

hackathons

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

mininet (ветвь mininet/mininet)

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

ntf

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

p4-applications

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

p4-build

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

p4-hlir

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

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

p4-spec

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

p4app

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

p4c

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

p4c-behavioral

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

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

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

p4c-bm

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

p4-constraints

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

p4factory

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

p4lang.github.io

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

p4ofagent

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

p4runtime

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

p4runtime-shell

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

papers

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

PI

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

protobuf (ветвь protocolbuffers/protobuf)

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

ptf

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

rules_protobuf (ветвь pubref/rules_protobuf)

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

SAI (ветвь opencomputeproject/SAI)

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

scapy-vxlan

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

switch

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

third-party

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

thrift (ветвь apache/thrift)

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

tutorials

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

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

nmalykh@protocols.ru

Рубрика: Сетевое программирование | Комментарии к записи Краткий обзор репозиториев P4 на Github отключены

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

image_print

PDF

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

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

/* Copyright 2013-present Barefoot Networks, Inc.

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

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

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

#ifndef __PSA_P4__
#define __PSA_P4__

#include<core.p4>

#ifndef _PORTABLE_SWITCH_ARCHITECTURE_P4_
#define _PORTABLE_SWITCH_ARCHITECTURE_P4_

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

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

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

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

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

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

const CloneSessionId_t PSA_CLONE_SESSION_TO_CPU = (CloneSessionId_t) 0;

#endif  // PSA_ON_BMV2_CORE_TYPES

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

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

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

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

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

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

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

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

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

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

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

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

struct psa_ingress_parser_input_metadata_t {
  PortId_t                 ingress_port;
  PSA_PacketPath_t         packet_path;
}

struct psa_egress_parser_input_metadata_t {
  PortId_t                 egress_port;
  PSA_PacketPath_t         packet_path;
}

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

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

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

@pure
extern bool psa_clone_i2e(in psa_ingress_output_metadata_t istd);

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


@pure
extern bool psa_resubmit(in psa_ingress_output_metadata_t istd);

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

@pure
extern bool psa_normal(in psa_ingress_output_metadata_t istd);

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

@pure
extern bool psa_clone_e2e(in psa_egress_output_metadata_t istd);

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// BEGIN:Random_extern
extern Random<T> {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// END:Programmable_blocks

#endif  /* _PORTABLE_SWITCH_ARCHITECTURE_P4_ */

#endif   // __PSA_P4__

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

nmalykh@protocols.ru

Рубрика: SDN, Сетевое программирование | Комментарии к записи Архитектура переносимых коммутаторов PSA отключены

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

image_print

PDF

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

/*
Copyright 2019 Orange

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

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

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

#ifndef _UBPF_MODEL_P4_
#define _UBPF_MODEL_P4_

#include <core.p4>

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

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


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

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

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

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

enum HashAlgorithm {
    lookup3
}

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

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

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

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

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

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

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

#endif /* _UBPF_MODEL_P4_ */

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

nmalykh@protocols.ru

Рубрика: SDN, Сетевое программирование | Комментарии к записи Архитектура uBPF отключены

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

image_print

PDF

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

/*
Copyright 2013-present Barefoot Networks, Inc.

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

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

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

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

#ifndef _CORE_P4_
#define _CORE_P4_

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

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

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

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

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

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

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

#endif  /* _CORE_P4_ */

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

nmalykh@protocols.ru

Рубрика: SDN, Сетевое программирование | Комментарии к записи Базовая библиотека P4 — core.p4 отключены

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

image_print

PDF

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

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

/*
Copyright 2013-present Barefoot Networks, Inc.

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

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

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

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

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

#ifndef _V1_MODEL_P4_
#define _V1_MODEL_P4_

#include "core.p4"

#ifndef V1MODEL_VERSION
#define V1MODEL_VERSION 20180101
#endif

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

const bit<32> __v1model_version = V1MODEL_VERSION;

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

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

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

enum CounterType {
    packets,
    bytes,
    packets_and_bytes
}

enum MeterType {
    packets,
    bytes
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

enum CloneType {
    I2E,
    E2E
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#endif  /* _V1_MODEL_P4_ */

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

nmalykh@protocols.ru

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

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

Рубрика: SDN, Сетевое программирование | Комментарии к записи Архитектура v1model отключены

Параметры simple_switch

image_print

PDF

Синтаксис

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

Опции

-h [ —help ]

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

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

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

—pcap [=arg(=.)]

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

—use-files arg

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

—packet-in arg

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

—thrift-port arg

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

—device-id arg

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

—nanolog arg

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

—log-console

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

—log-file arg

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

-L [ —log-level ] arg

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

—log-flush

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

—notifications-addr arg

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

—restore-state arg

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

—dump-packet-data arg

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

-v [ —version ]

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

—json-version

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

—no-p4

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

—max-port-count arg (=512)

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

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

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

—load-modules arg

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

—enable-swap

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

—drop-port arg

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

Рубрика: SDN, Сетевое программирование | Комментарии к записи Параметры simple_switch отключены

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

image_print

PDF

Синтаксис

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

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

Опции

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

-h, —help

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

-V, —version

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

-v, —debug

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

-###, —test-only

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

-Xpreprocessor <arg>

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

-Xp4c <arg>

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

-Xassembler <arg>

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

-Xlinker <arg>

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

-b TARGET, —target TARGET

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

-a ARCH, —arch ARCH

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

-c, —compile

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

-D PREPROCESSOR_DEFINES

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

-E

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

-e

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

-g

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

-I SEARCH_PATH

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

-o PATH, —output PATH

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

—p4runtime-file P4RUNTIME_FILE

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

—p4runtime-files P4RUNTIME_FILES

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

—p4runtime-format {binary,json,text}

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

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

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

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

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

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

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

-S

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

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

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

—ndebug

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

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

nmalykh@protocols.ru

Рубрика: SDN, Сетевое программирование | Комментарии к записи Компилятор P4C (драйвер компиляции) отключены