RISC-V P4

PDF

Постановка задачи

Для выполнения перспективных работ возникла задача проверки работоспособности приложений P4 и компилятора P4C на аппаратной платформе с процессором RISC-V. В качестве платформы была выбрана плата HiFive Unleashed производства компании SiFive. Для этой платформы имеется ряд SDK, основанных на ОС Linux и доступных в исходном коде. После ряда экспериментов было выбрано в качестве основы решение SiFive Freedom Unleashed SDK на основе среды разработки OpenEmbedded (Yocto, OE). Репозиторий исходных кодов включает компоненты, оптимизированные для платы HiFive Unleashed, что позволило сразу же перейти к созданию образа с нужными компонентами P4. Создание и установка базового образа с компонентами P4 были описаны ранее. Здесь же более подробно рассматривается текущее состояние использованных компонент, возникшие проблемы и возможные способы их решения. Сборка образа выполнялась в среде Mageia Linux v7.1.

Набор компонент

Для экспериментов были выбраны модель BMV2 с библиотекой PI и компилятор P4C. Все эти компоненты зависят от библиотеки Judy, поэтому работа началась со сборки этой библиотеки, отсутствующей в репозитории meta-sifive.

Библиотека Judy

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

В процессе компиляции пакета создаются два исполняемых файла (JudyLTablesGen и Judy1TablesGen), которые запускаются для генерации таблиц (файлы C), применяемых далее в процессе компиляции. Проблема заключается в том, что создаются исполняемые файлы для процессора RISC-V, а запускаются они в среде кросс-компиляции и, естественно, не могут работать. Эта проблема известна уже давно (см., например, https://sourceforge.net/p/judy/bugs/21/ и https://www.linuxquestions.org/questions/linux-software-2/cross-compiling-libjudy-608455/), но решения найти не удалось, поэтому был выбран другой подход, представляющийся более реальным.

Библиотека была собрана непосредственно на платформе HiFive Unleashed1 и созданные таблицы были перенесены в среду кросс-компиляции, а запуск программ генерации таблиц был исключен из соответствующих файлов Makefile. Собранная в результате библиотека Judy работает при загрузке образа на платформе HiFive Unleashed.

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

Библиотека PI

PI представляет собой набор API (исходный код) для взаимодействия с объектами, определенными в программах P4 (таблицы, счетчики, измерители). Для сборки и работы требуется выполнить ряд зависимостей, включая пакет behavioral-model (bmv2). Но для сборки этого пакета требуется наличие PI. В результате образуется циклическая зависимость, которая не позволяет собрать пакеты в среде OE. Приходиться отказаться от поддержки BMV2. Остальная часть настройки и сборки проходит без проблем и пакет удается включить в образ.

Пакет BMV2

Этот пакет (исходный код) включает прототипы коммутаторов и маршрутизатора, работающих на основе кода P4. Некоторые фрагменты кода P4 представлены в примерах. Настройка и сборка пакета серьезных проблем не вызвали, пока не была предпринята попытка включить библиотеку Apache Thrift, без которой собирались лишь библиотеки, но не исполняемые программы (simple_switch и др.), что нас явно не устроило.

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

| checking dynamic linker characteristics... (cached) GNU/Linux ld.so 
| checking how to hardcode library paths into programs... immediate 
| checking whether riscv64-oe-linux-g++    -fstack-protector-strong  -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=/OE/sifive-new/build/tmp-glibc/work/riscv64-oe-linux/bm/1.13.0+
gitAUTOINC+9a331b900c-r0/recipe-sysroot supports C++11 features by default... yes 
| checking for thrift... no 
| checking thrift/Thrift.h usability... no 
| checking thrift/Thrift.h presence... no 
| checking for thrift/Thrift.h... no 
| configure: error: Thrift headers not found. Install Thrift from http://thrift.apache.org/docs/install/ 
| WARNING: /OE/sifive-new/build/tmp-glibc/work/riscv64-oe-linux/bm/1.13.0+gitAUTOINC+9a331b900c-r0/temp/run.do_configure.3659:1 exit 1 from 'exit 1' 
ERROR: Task (/OE/sifive-new/meta-poPingUI/recipes-p4/bm/bm_git.bb:do_configure) failed with exit code '1'

Явное добавление в файл задания зависимости от thrift и thrift-native проблему не решило.

| checking for thrift... /OE/sifive-new/build/tmp-glibc/work/riscv64-oe-linux/bm/1.13.0+gitAUTOINC+9a331b900c-r0/recipe-sysroot-native/usr/bin/thrift 
| checking thrift/Thrift.h usability... yes 
| checking thrift/Thrift.h presence... yes 
| checking for thrift/Thrift.h... yes 
| checking thrift/stdcxx.h usability... no 
| checking thrift/stdcxx.h presence... no 
| checking for thrift/stdcxx.h... no 
| checking for thrift version... configure: error: in `/OE/sifive-new/build/tmp-glibc/work/riscv64-oe-linux/bm/1.13.0+gitAUTOINC+9a331b900c-r0/build': 
| configure: error: cannot run test program while cross compiling 
| See `config.log' for more details 
| WARNING: /OE/sifive-new/build/tmp-glibc/work/riscv64-oe-linux/bm/1.13.0+gitAUTOINC+9a331b900c-r0/temp/run.do_configure.9810:1 exit 1 from 'exit 1'

При просмотре журнала настройки конфигурации сборки (config.log) подтвердилось, что кросс-компиляция пакета не поддерживается.

configure:16615: error: cannot run test program while cross compiling

Таким образом, создание полноценного пакета BMV2 в среде кросс-компиляции оказалось невозможным и остается лишь собирать пакет непосредственно на платформе HiFive Unleashed.

Компилятор P4C

Пакет P4C представляет собой прототип компилятора, поддерживаюзий спецификации P414 и P416. При попытке собрать пакет в кросс-среде OE возникли проблемы, аналогичные ситуации с библиотеков Judy, описанной выше. Здесь также генерируется ряд файлов исходного кода с помощью созданной в процессе компиляции программы. Путем переноса файлов, созданных при сборке на платформе HiFive Unleashed и исключения одной строки из файла build.ninja, управляющего сборкой, проблему удалось решить.

Заключение

Проведенные эксперименты показывают, что собрать образ Linux для платы HiFive Unleashed (это справедливо и для других плат) с поддержкой P4 в среде кросс-компиляции OpenEmbedded на сегодняшний день не представляется возможным. Для решения этой задачи требуется внести достаточно серьезные изменения в исходный код ряда компонент и библиотек.

Работа выполнена в рамках проекта «Орион».

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

nmalykh@protokols.ru

1Может возникнуть резонный вопрос — почему не собрать все компоненты непосредственно на платформе, коль скоро имеется набор инструментальных средств. Ответ достаточно прост и безрадостен — сборка P4C на платформе занимает больше 10 часов, поэтому оказалось проще перенести файлы в среду сборки, где производительность во много раз выше.

2Она включена по умолчанию и для отключения нужна опция —without-thrift.

Рубрика: Linux, RISC-V, SDN, Сетевое программирование | Комментарии к записи RISC-V P4 отключены

Сетевой стек thrift

PDF

Оригинал

Простое представление сетевого стека Apache Thrift приведено на рисунке

  +-------------------------------------------+
  | Server                                    |
  | (однопотоковый, управляемый по событиям ) |
  +-------------------------------------------+
  | Processor                                 |
  | (создан компилятором)                     |
  +-------------------------------------------+
  | Protocol                                  |
  | (JSON, compact и т. п.)                   |
  +-------------------------------------------+
  | Transport                                 |
  | (raw TCP, HTTP и т. п.)                   |
  +-------------------------------------------+

Transport

Транспортный уровень обеспечивает простую абстракцию чтения-записи из сети или в сеть. Это позволяет Thrift отвязать нижележащий (базовый) транспорт от остальной части системы (например, сериализации и десериализации). Интерфейс Transport обеспечивает ряд методов (неполный список):

  • open;

  • close;

  • read;

  • write;

  • flush.

В дополнение к интерфейсу Transport Thrift использует интерфейс ServerTransport для восприятия и создания примитивов транспортных объектов. Этот интерфейс применяется в основном на серверной стороне для создания новых транспортных объектов при входящих соединениях. Интерфейс ServerTransport поддерживает методы:

  • open;

  • listen;

  • accept;

  • close.

Имеется несколько вариантов транспорта для большинства поддерживаемых в Thrift языков:

  • file — чтение и запись дисковых файлов;

  • http — взаимодействие по протоколу http.

Protocol

Абстракция протокола обеспечивает механизм отображения хранящихся в памяти структур данных в «проводной» формат (wire-format). Иными словами, протокол определяет использование типами данных нижележащего транспорта для кодирования и декодирования себя. Таким образом, реализация протокола управляет схемой кодирования и отвечает за сериализацию и десериализацию. Примеры протоколов включают JSON, XML, plain text, compact binary.

Функции интерфейса Protocol указаны ниже.

writeMessageBegin(name, type, seq)
writeMessageEnd()
writeStructBegin(name)
writeStructEnd()
writeFieldBegin(name, type, id)
writeFieldEnd()
writeFieldStop()
writeMapBegin(ktype, vtype, size)
writeMapEnd()
writeListBegin(etype, size)
writeListEnd()
writeSetBegin(etype, size)
writeSetEnd()
writeBool(bool)
writeByte(byte)
writeI16(i16)
writeI32(i32)
writeI64(i64)
writeDouble(double)
writeString(string)

name, type, seq = readMessageBegin()
                  readMessageEnd()
name = readStructBegin()
       readStructEnd()
name, type, id = readFieldBegin()
                 readFieldEnd()
k, v, size = readMapBegin()
             readMapEnd()
etype, size = readListBegin()
              readListEnd()
etype, size = readSetBegin()
              readSetEnd()
bool = readBool()
byte = readByte()
i16 = readI16()
i32 = readI32()
i64 = readI64()
double = readDouble()
string = readString()

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

  • binary — достаточно простое двоичное кодирование, где размер и тип поля представляются байтами, за которыми следуют значения полей;

  • compact — см. THRIFT-110;

  • json.

Processor

Уровень Processor инкапсулирует чтение данных из входного потока и запись в выходной поток. Потоки на входе и выходе представляются объектами уровня Protocol. Интерфейс Processor крайне прост и имеет вид

interface TProcessor {
    bool process(TProtocol in, TProtocol out) throws TException
}

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

Server

Сервер объединяет перечисленные выше функции:

  • создание транспорта;

  • создание протоколов ввода-вывода для транспорта;

  • создание процессора на основе протоколов ввода-вывода;

  • ожидание входящих вызовов и передача их процессору.


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

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

nmalykh@protokols.ru

Рубрика: Linux | Комментарии к записи Сетевой стек thrift отключены

BMv2 simple_switch

PDF

Оригинал

Модель bmv2 позволяет разработчикам реализовать свою архитектуру программируемого коммутатора на основе P4. Архитектура simple_switch подходит для большинства пользователей, поскольку она близка к абстрактной модели коммутатора, описанной в спецификации P414.

Язык P416 поддерживает разную архитектуру, например, несколько вариантов архитектуры для одного коммутатора, адаптера NIC1 и т. п. Архитектура v1model в составе компилятора p4c была разработана в соответствии с архитектурой коммутатора P414, что упрощает автоматическую трансляцию программ P414 в программы P416 с архитектурой v1model. Имеется несколько различий между P414 и v1model, описанных ниже (прежде всего, имена метаданных).

Язык P416 сейчас включает переносимую архитектуру коммутации PSA2 определенную в отдельной спецификации. Архитектура PSA к октябрю 2019 г. уже была частично реализована, но пока не завершена. Она будет выполнена в виде программы psa_switch отдельно от описанной здесь программы simple_switch.

Этот документ описывает архитектуру simple_switch для программистов P4.

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

Для программ P416, использующих архитектуру v1model и включающих файл v1model.p4, все описанные ниже поля являются частью структуры standard_metadata_t. Для программ P414 описанные ниже поля являются частью предопределенного заголовка standard_metadata.

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

  • sm14 определено в спецификации P414 v1.0.4 (раздел 6 «Standard Intrinsic Metadata»);

  • v1m определено в файле p4include/v1model.p4 репозитория p4c и предназначено для программ P416, собранных для архитектуры v1model.

Поля метаданных перечислены ниже.

ingress_port (sm14, v1m)

Для новых пакетов указывает номер порта, принявшего пакет (только чтение).

packet_length (sm14, v1m)

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

egress_spec (sm14, v1m)

Может задаваться входным кодом для указания выходного порта. Примитивы P414 drop и v1model mark_to_drop имеют побочный эффект назначения этому полю зависящего от реализации значения DROP_PORT3, в результате чего пакет будет отброшен в конце входной обработки без сохранения в буфере и передачи на выходную обработку. Относительный приоритет этого поля по сравнению с другими операциями рассмотрен в параграфе «Псевдокод для завершения входной и выходной обработки». Если программа P4 назначает egress_spec = DROP_PORT, ей следует выполнять процедуру after-ingress, даже если программа никогда не вызывает mark_to_drop (P416) или drop (P414).

egress_port (sm14, v1m)

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

egress_instance (sm14)

Переименованное поле egress_rid в simple_switch.

instance_type (sm14, v1m)

Содержит значение, которое может прочитать код P4. Во входном коде это значение позволяет определить, пришел пакет из порта (NORMAL) или является результатом примитива повторного представления (RESUBMIT) или рециркуляции (RECIRC). При выходной обработке может служить для определения был пакет обработан в результате примитива клонирования ingress-to-egress (INGRESS_CLONE), egress-to-egress (EGRESS_CLONE), групповой репликации при входной обработке (REPLICATION) или является обычным индивидуальным пакетом со входа (NORMAL). Пока подобные константы не являются предопределенными, можно применять этот список.

parser_status (sm14) илиparser_error (v1m)

Поле parser_status задано в спецификации P414 и переименовано в parser_error в v1model. Значение 0 (sm14) или error.noError (P416 + v1model) говорит об отсутствии ошибок. Остальные значения указывают код ошибки.

parser_error_location (sm14)

Отсутствует в v1model.p4 и не реализовано в simple_switch.

checksum_error (v1m)

Доступно лишь для чтения. 1 указывает ошибку контрольной суммы при вызове verify_checksum, иначе поле содержит 0. Вызовы verify_checksum для v1model следует выполнять в элементе VerifyChecksum после анализа, но до входной обработки.

Внутренние метаданные

Каждая архитектура обычно определяет внутренние поля метаданных, используемые в дополнение к стандартным метаданным для расширения функциональности. В simple_switch используется 2 внутренних заголовка метаданных. Архитектура не требует этих заголовков и программу P4 можно написать и запустить в simple_switch без них. Однако эти заголовки нужны для включения некоторых функций simple_switch. Для большинства полей нет строгих требований по размеру, но рекомендуется следовать приведенным ниже описаниям. Некоторые поля доступны напрямую (чтение или/и запись), другие — только через примитивы действий.

Заголовок intrinsic_metadata

Для программ P416 с архитектурой v1model, включающих файл v1model.p4, все перечисленные ниже поля являются частью структуры standard_metadata_t и определять свою структуру для них не требуется. Для программ P414 рекомендуется определять и создавать показанный ниже заголовок в каждой программе P4 для архитектуры simple_switch.

header_type intrinsic_metadata_t {
     fields {
         ingress_global_timestamp : 48;
         egress_global_timestamp : 48;
         mcast_grp : 16;
         egress_rid : 16;
     }
}
metadata intrinsic_metadata_t intrinsic_metadata;

ingress_global_timestamp

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

egress_global_timestamp

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

mcast_grp

Требуется для поддержки групповой адресации. Поле должно записываться во входном конвейере, когда пакет будет сочтен групповым (0 если нет). Значение поля должно соответствовать одной из multicast-групп, настроенных на интерфейсах bmv2 в процессе работы. Приоритет по сравнению с другими операциями в конце входной обработки описан в параграфе «Псевдокод для завершения входной и выходной обработки».

egress_rid

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

Заголовок queueing_metadata

Для программ P416 с архитектурой v1model, включающих файл v1model.p4, описанные поля являются частью структуры типа standard_metadata_t. Не требуется определять для этих полей свою структуру. Для программ P414 нужно определять этот заголовок P4, если нужен доступ к информации об очередях (пакет помещается в очередь между входным и выходным конвейером). Заголовок нужно определять полностью или не использовать совсем. Для создания заголовка рекомендуется приведенный ниже код P414.

header_type queueing_metadata_t {
     fields {
         enq_timestamp : 48;
         enq_qdepth : 16;
         deq_timedelta : 32;
         deq_qdepth : 16;
         qid : 8;
     } 
} 
metadata queueing_metadata_t queueing_metadata;

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

enq_timestamp

Временная метка (мксек) первого включения пакета в очередь.

enq_qdepth

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

deq_timedelta

Время (мксек) нахождения пакета в очереди.

deq_qdepth

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

qid

При наличии множества очередей (например, по приоритетам) на каждом выходном порту каждой очереди присваивается уникальный идентификатор, который записывается в это поле. В остальных случаях поле имеет значение 0. Отметим, что поле qid не входит в тип standard_metadata_t модели v1model.

Поддерживаемые примитивы действий

Поддерживаются основные стандартные примитивы действий P414. Однако необязательные параметры не поддерживаются в bmv2, поэтому все указанные параметры являются обязательными. Полный список примитивов можно найти в файле primitives.cpp.

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

После входного конвейера (after-ingress) — краткая форма.

if (был вызван примитив clone) {
     создается клон(ы) пакета в соответствии с сессией clone
} 
if (создается digest) {   // код вызывает generate_digest
     передается сообщение digest программам плоскости управления 
} 
if (был вызван примитив resubmit) {
     повтор входной обработки исходного пакета 
} else if (mcast_grp != 0) {  // поскольку код присвоил значение mcast_grp
     групповая передача пакета в выходные порты группы mcast_grp 
} else if (egress_spec == DROP_PORT) {  // например, в результате вызова drop/mark_to_drop
     отбрасывание пакета } else {
     индивидуальная передача пакета в порт egress_spec 
}

После входного конвейера (after-ingress) для определения, что произойдет с пакетом после завершения входной обработки (более подробная форма).

if (был вызван примитив clone) {
    // Это будет выполняться при вызове кодом примитива clone или clone3 
    // из программы P4_16 или примитива clone_ingress_pkt_to_egress
    // из программы P4_14 в процессе входной обработки.

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

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

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

    Для действий clone3 (P4_16) и clone_ingress_pkt_to_egress (P4_14)
    также сохраняются финальные входные значения полей метаданных,
    заданных в списке аргумента, за исключением установки в поле
    instance_type значения PKT_INSTANCE_TYPE_INGRESS_CLONE.

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

    После анализа обработка каждого клона продолжается от начала
    выходного кода.
    // Выполняется приведенный ниже код
}
if (создается digest) {
    // Выполняется при вызове кодом примитива generate_digest в
    // процессе входной обработки.
    Уровню управления передается сообщение digest со значениями полей,
    заданных в списке.
    // Выполняется приведенный ниже код
}
if (был вызов resubmit) {
    // Выполняется при вызове кодом примитива resubmit в процессе
    // входной обработки.
    Снова выполняется входная обработка пакета с неизменным содержимым
    и метаданными. Сохраняются финальные выходные значения всех полей,
    указанных в списке аргумента последнего вызова примитива resubmit(), 
    за исключением назначения instance_type = PKT_INSTANCE_TYPE_RESUBMIT.
} else if (mcast_grp != 0) {
    // Выполняется при установке кодом standard_metadata.mcast_grp в
    // процессе входной обработки. В simple_switch нет специального
    // примитива для этого. Используется стандартный оператор присваивания
    // в P4_16 или примитив P4_14 modify_field().
    Могут создаваться копии пакета на базе списка пар (egress_port, egress_rid),
    заданного уровнем управления для значения mcast_grp value. Каждая копия
    помещается в соответствующую очередь. В поле instance_type каждой копии
    будет значение PKT_INSTANCE_TYPE_REPLICATION.
} else if (egress_spec == DROP_PORT) {
    // Выполняется при вызове кодом примитива mark_to_drop (P4_16) или drop (P4_14)
    // в процессе входной обработки.
    Пакет отбрасывается.
} else {
    Помещается в очередь каждая копия для порта egress_port = egress_spec.
}

После выходного конвейера (after-egress) — краткая форма.

if (был вызван примитив clone) {
    Создаются клоны пакета в соответствии с настройками сессии clone.
}
if (egress_spec == DROP_PORT) {  // например, при вызове drop/mark_to_drop
    Пакет отбрасывается.
} else if (был вызван примитив recirculate) {
    Начинается повторная входная обработка проанализированного пакета.
} else {
    Пакет передается в порт egress_port.
}

После выходного конвейера (after-egress) для определения действий после завершения выходной обработки (более подробная форма).

if (был вызван примитив clone) {
    // Выполняется при вызове примитива clone или clone3 в коде P4_16 или
    // clone_egress_pkt_to_egress в P4_14 при выходной обработке.

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

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

    Содержимое клонов совпадает с содержимым в конце выходной обработки,
    включая изменение значений полей заголовка, с учетом пригодности
    заголовков. Синтезатор (deparser) не будет применяться для клонов
    egress-to-egress, равно как и анализатор.

    Если выполнялось действие clone3 (P4_16) или clone_egress_pkt_to_egress 
    (P4_14), сохраняются также финальные выходные значения полей метаданных,
    указанных в списке аргумента, за исключением установки в instance_type
    значения PKT_INSTANCE_TYPE_EGRESS_CLONE. Каждый клон будет иметь поля
    standard_metadata, переопределенные в начале выходной обработки
    (например, egress_port, egress_spec, egress_global_timestamp и т. п.).

    Обработка каждого клона будет продолжаться с начала выходного конвейера.
    // Выполняется приведенный ниже код
}
if (egress_spec == DROP_PORT) {
    // Выполняется при вызове примитива mark_to_drop (P4_16) или drop (P4_14) 
    // в процессе выходной обработки.
    Пакет отбрасывается.
} else if (был вызван примитив recirculate) {
    // Выполняется при вызове recirculate во время выходной обработки.
    Заново выполняется входная обработка пакета, как созданного синтезатором,
    со всеми изменениями в процессе входной и выходной обработки. Сохраняются
    финальные выходные значения полей, заданных в списке аргумента при 
    последнем вызове примитива recirculate, за исключением установки в поле
    instance_type значения PKT_INSTANCE_TYPE_RECIRC.
} else {
    Пакет передается в выходной порт egress_port. Поскольку поле egress_port 
    доступно при выходной обработке лишь для чтения, отметим, что его значение
    должно быть определено в процессе входной обработки для обычных пакетов. 
    Единственным исключением является выполнение примитива clone при выходной
    обработке, где egress_port определяется уровнем управления для сеанса clone.
}

Поддерживаемые типы таблиц соответствия

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

  • exact из спецификации P416;
  • lpm (longest prefix match) из спецификации P416;
  • ternary из спецификации P416;
  • optional из v1model.p4;
  • range из v1model.p4;
  • selector из v1model.p4.

Поле selector поддерживается только для таблиц с реализацией действия selector.

Если таблица имеет более одного ключевого поля lpm, она отвергается реализацией p4c BMv2. Это может быть слегка обобщено, как описано ниже, но данное ограничение поддерживается в январской (2019 г.) версии p4c.

Таблицы range

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

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

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

Таблицы ternary

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

Таблицы lpm

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

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

Таблицы exact

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

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

Задание критериев поиска с помощью записей const

В приведенной ниже таблице для каждого значения match_kind и ключевых полей таблиц P416 показан синтаксис, разрешенный при указании набора полей сопоставления для записи таблицы в списке записей const. Ограничения состоят в том, что во всех разрешенных случаях lo, hi, val и mask должны иметь дозволенные положительные значения, т. е. не выходить за пределы разрешенных для поля диапазонов. Все элементы могут быть арифметическими выражениями, преобразуемыми в константы в момент компиляции.

 

range

ternary

optional

lpm

exact

lo .. hi

да4

нет

нет

нет

нет

val &&& mask

нет

да5

нет

да6

нет

val

да7

да8

да

да5

да

_ или default

да9

да6

да6

да6

нет

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

header h1_t {
    bit<8> f1;
    bit<8> f2;
}

struct headers_t {
    h1_t h1;
}

// ... позднее ...

control ingress(inout headers_t hdr,
                inout metadata_t m,
                inout standard_metadata_t stdmeta)
{
    action a(bit<9> x) { stdmeta.egress_spec = x; }
    table t1 {
        key = { hdr.h1.f1 : range; }
        actions = { a; }
        const entries = {
             1 ..  8 : a(1);
             6 .. 12 : a(2);  // диапазоны в записях могут перекрываться
            15 .. 15 : a(3);
            17       : a(4);  // эквивалентно 17 .. 17
            // Правило «соответствия всему» в таблице не требуется, но разрешено
            // (за исключением точного совпадения) и в некоторых примерах оно есть.
            _        : a(5);
        }
    }
    table t2 {
        key = { hdr.h1.f1 : ternary; }
        actions = { a; }
        // Не требуется задавать критерии сопоставления ternary шестнадцатеричными
        // значениями. Просто иногда это удобней.
        const entries = {
            0x04 &&& 0xfc : a(1);
            0x40 &&& 0x72 : a(2);
            0x50 &&& 0xff : a(3);
            0xfe          : a(4);  // эквивалентно 0xfe &&& 0xff
            _             : a(5);
        }
    }
    table t3 {
        key = {
            hdr.h1.f1 : optional;
            hdr.h1.f2 : optional;
        }
        actions = { a; }
        const entries = {
            // Отметим, что при наличии в таблице нескольких ключей записи const
            // для выражений выбора ключа должны заключаться в круглые скобки.
            (47, 72) : a(1);
            ( _, 72) : a(2);
            ( _, 75) : a(3);
            (49,  _) : a(4);
            _        : a(5);
        }
    }
    table t4 {
        key = { hdr.h1.f1 : lpm; }
        actions = { a; }
        const entries = {
            0x04 &&& 0xfc : a(1);
            0x40 &&& 0xf8 : a(2);
            0x04 &&& 0xff : a(3);
            0xf9          : a(4);  // эквивалентно 0xf9 &&& 0xff
            _             : a(5);
        }
    }
    table t5 {
        key = { hdr.h1.f1 : exact; }
        actions = { a; }
        const entries = {
            0x04 : a(1);
            0x40 : a(2);
            0x05 : a(3);
            0xf9 : a(4);
        }
    }
    // ... остальной код ...
}

Ограничения для операций recirculate, resubmit и clone

Операции recirculate, resubmit и clone не сохраняют метаданные (т. е. имеют пустой список полей, которые следует сохранить) и работают должным образом при использовании p4c и simple_switch с P414 или P416 + архитектура v1model.

К сожалению часть операций, пытающихся сохранить метаданные, не работает корректно и они по-прежнему вызывают для пакетов действия recirculate, resubmit или clone, не сохраняя желаемые значения метаданных. Эта проблема подробно обсуждалась разработчиками p4c и рабочей группой P4 в результате чего был разработан ряд предложений.

  • В долгосрочной перспективе архитектура P416 PSA использует механизм указания сохраняемых полей метаданных, отличающийся от v1model, и должна работать корректно. По состоянию на октябрь 2019 г. реализация PSA не завершена, поэтому сегодня она не помогает создавать рабочий код.
  • Желающим внести изменения в p4c и/или simple_switch, решающие эту проблему, следует обращаться в рабочую группу P4 для координации действий.
    • Предпочтительной формой помощи является завершение реализации архитектуры PSA.
    • Другим вариантом является совершенствование реализации архитектуры v1model. Некоторые из подходов к решению этой задачи рассматриваются здесь.

Эта проблема затрагивает не только программы P416, использующие архитектуру v1model, но и программы P414, использующие компилятор p4c, поскольку он транслирует код P414 в код P416 перед использованием той части компилятора, с которой связана проблема.

Фундаментальная проблема заключается в том, что P414 имеет конструкцию field_list, которая ограничивает именованные ссылки на другие поля. Когда эти списки используются в P414 для операций recirculate, resubmit или clone, которые сохраняют метаданные, они указывают цель не только для чтения этих полей, но и для их записи (пакет после операции recirculate, resubmit или clone). Списки полей P416, использующие синтаксис {field_1, field_2, …}, ограничены доступом к текущим значениям полей во время выполнения оператора или выражения, но не представляют какие-либо ссылки и в соответствии со спецификацией P416 значения полей не могут быть изменены в другой части программы.

Советы по определению корректности сохранения метаданных

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

Python-программа bmv2-json-check.py пытается определить, похож ли какой-либо список полей для сохранения метаданных при вызове recirculate, resubmit или clone operations на имя созданной компилятором временной переменной. Используемые программой методы очень просты и не дают гарантии корректности результата. Программа лишь автоматизирует описанные ниже проверки.

Каждая операция recirculate, resubmit и clone представлена данными JSON в файле BMv2 JSON. Отыскиваются приведенные ниже строки в двойных кавычках:

  • «recirculate» — единственным параметром является идентификатор field_list;
  • «resubmit» — единственным параметром является идентификатор field_list;
  • «clone_ingress_pkt_to_egress» — вторым параметром является идентификатор field_list;
  • «clone_egress_pkt_to_egress» — вторым параметром является идентификатор field_list.

Эти поля можно найти в каждом списке полей раздела файла BMv2 JSON с ключом field_lists. Программа simple_switch будет сохранять значения полей с этими именами, независимо от имен. Основная проблема заключается в соответствии или несоответствии месту, используемому компилятором для представления полей, которые нужно сохранить. В некоторых случаях они совпадают (часто для имен полей в файле BMv2 JSON, которые похожи или совпадают с именами полей в исходном коде P4), а в иных случаях представляется другое местоположение (например, созданные компилятором временные переменные для хранения полей, которые нужно сохранить). Имена полей, начинающиеся с tmp., намекают на реализацию p4c октября 2019 г., но это деталь реализации p4c, которая может измениться.

Замечания по архитектуре P416 + v1model

Ниже приведено описание работы P416 с архитектурой v1model. В некоторых случаях описанные детали относятся к реализации архитектуры v1model в BMv2 simple_switch и это отмечено явно, поскольку поведение (ограничения) других реализаций могут отличаться.

Ограничения для типа H

Тип H является параметром определения пакета V1Switch в файле v1model.p4, показанного ниже.

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
                       );

Этот тип H может быть структурой, содержащей элементы одного из перечисленных типов (но не иные):

  • header;

  • header_union;

  • стек header.

Ограничения для кода анализатора

Спецификация языка P416 версии 1.1 не поддерживает условный оператор if внутри анализатора (parser). Поскольку компилятор p4c использует in-line-функции в точках вызова, это ограничение распространяется на тела функций вызываемых из кода анализатора. Есть два способа обойти это ограничение:

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

В архитектуре v1model пакет, достигший состояния reject в анализаторе (например, в результате запрета соединений), не отбрасывается автоматически. Для пакета начинается входная обработка (Ingress) со значением поля стандартных метаданных parser_error в соответствии с обнаруженной ошибкой. Код P4 может отбрасывать такие пакеты или выполнять иные действия (например, передать клон пакета управляющему CPU для анализа и записи события).

Комбинация p4c и simple_switch не поддерживает явного перехода в состояние reject в исходном коде. Это можно сделать лишь через отказ при проверке вызова.

Ограничения для кода VerifyChecksum

Элемент VerifyChecksum выполняется после завершения работы анализатора (Parser) и до начала входной обработки (Ingress). Комбинация p4c и simple_switch поддерживает вызовы внешней функции verify_checksum или verify_checksum_with_payload лишь внутри этих элементов (см. определение в файле v1model.p4). Первый аргумент этих функций является логическое значение (условие), которое может использоваться для условного расчета контрольной суммы.

В архитектуре v1model пакеты с некорректной контрольной суммой не отбрасываются автоматически. Для такого пакета начинается входная обработка (Ingress) со значением стандартного поля метаданных checksum_error = 1. Если в программе используется множество вызовов verify_checksum или verify_checksum_with_payload, нет возможности определить, какой из этих вызовов определил некорректную сумму. Код P4 может отбрасывать такие пакеты, но это не делается автоматически.

Ограничения для кода элементов Ingress и Egress

Программа simple_switch не поддерживает неоднократное использование одной таблицы в элементе Ingress или Egress. В некоторых случаях это ограничение можно обойти путем создания нескольких таблиц с похожими определениями и однократного применения каждой таблицы в процессе выполнения элемента. Если нужно создать две таблицы с одинаковыми записями и действиями, программы уровня управления должны сохранять содержимое этих таблиц одинаковым (например, всегда добавлять запись в T2 при добавлении ее в T1).

Это решение можно было бы обобщить в simple_switch, но следует понимать, что некоторые высокоскоростные реализации ASIC с P4 могут вносить такое же ограничение на аппаратном уровне.

Ограничения для кода действий

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

Спецификация P416 версии v1.1.0 разрешает операторы if внутри объявления действий. При использовании p4c для компиляции BMv2 simple_switch поддерживаются некоторые типы операторов if, в частности, те, которые могут быть преобразованы в назначения с использованием оператора condition ? true_expr : false_expr. Будет работать действие

    action foo() {
        meta.b = meta.b + 5;
        if (hdr.ethernet.etherType == 7) {
            hdr.ethernet.dstAddr = 1;
        } else {
            hdr.ethernet.dstAddr = 2;
        }
    }

Но не будет работать (по состоянию на 1 июля 2019 г.)

    action foo() {
        meta.b = meta.b + 5;
        if (hdr.ethernet.etherType == 7) {
            hdr.ethernet.dstAddr = 1;
        } else {
            mark_to_drop(standard_metadata);
        }
    }

С учетом приведенной ниже выдержки из спецификации P416 очевидна возможность наличия реализаций P4, которые могут ограничивать и не поддерживать операторы if внутри действий.

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

Таким образом, программы P4 с операторами if в коде действий явно будут менее переносимыми. Как отмечено выше, расширение p4c позволило бы поддержать использование операторов if в коде действий.

Ограничения для кода ComputeChecksum

Элемент ComputeChecksum выполняется сразу по завершении элемента Egress, но до начала работы Deparser. Комбинация p4c и simple_switch поддерживает последовательность вызовов внешних функций update_checksum и update_checksum_with_payload лишь внутри таких элементов. Первым аргументом этих функций является логическое значение, которое может применяться для условного расчета контрольной суммы.

Ограничения для кода Deparser

Элемент Deparser может включать лишь последовательность вызовов метода emit объекта packet_out.

Самый простой способ избежать ограничений в элементах ComputeChecksum и Deparser заключается в создании максимально общего кода в конце элемента Egress.

Реализация регистров в BMv2

Как p4c, так и simple_switch поддерживают массивы регистров (register) с произвольными значениями типов bit<W> или int<W>, но не других типов (например, тип struct был бы удобен в некоторых программах, но не поддерживается). В качестве примера обхода этого ограничения предположим, что нужна структура с полями x, y, z типов bit<8>, bit<3>, bit<6>. Этого можно избежать, создав массив register с элементами типа bit<17> (общий размер трех полей) и используя операции «нарезки» (slicing) битов P416 для выделения полей из 17-битового значения после чтения массива регистров. Перед записью в массив можно использовать операции конкатенации векторов P416 для слияния трех полей в 17-битовое значение.

    register< bit<17> >(512) my_register_array;
    bit<9> index;

    // ... другой код ...

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

    action update_fields() {
        bit<17> tmp;
        bit<8> x;
        bit<3> y;
        bit<6> z;

        my_register_array.read(tmp, (bit<32>) index);
        // Используется нарезка битов для извлечения логически разделенных
        // частей 17-битового значения регистра.
        x = tmp[16:9];
        y = tmp[8:6];
        z = tmp[5:0];

        // Здесь вносятся все изменения в переменные x, y и z Это просто
        // пример кода, не выполняющий реальной обработки пакетов.
        if (y == 0) {
            x = x + 1;
            y = 7;
            z = z ^ hdr.ethernet.etherType[3:0];
        } else {
            // x не меняется
            y = y - 1;
            z = z << 1;
        }

        // Конкатенация измененных значений x, y, z в 17-битовое значение
        // для записи в регистр.
        tmp = x ++ y ++ z;
        my_register_array.write((bit<32>) index, tmp);
    }

Хотя p4c и simple_switch поддерживают битовый размер W, достаточный для обработки пакетов, Thrift API (используется в simple_switch_CLI и возможно в некоторых программах контроллеров) поддерживает для уровня управления чтение и запись лишь до 64 битов (см. тип BmRegisterValue в файле standard.thrift, который задавал 64-битовое целое число по состоянию на октябрь 2019 г.). P4Runtime API не имеет этого ограничения, но пока в P4Runtime нет реализации чтения и записи для simple_switch (p4lang/PI#376)

BMv2 v1model поддерживает параллельную работу, блокируя все объекты register, доступные из действия, для предотвращения конфликтов с другими операциями. Для этого не нужно использовать аннотацию @atomic в программах P416. BMv2 v1model (10.2019) игнорирует аннотации @atomic в программах P416. Даже при использовании таких аннотаций BMv2 не будет считать любой блок кода, превышающий один вызов действия, атомарной транзакцией.

Реализация случайных значений в BMv2

Реализация в BMv2 v1model функции random поддерживает в параметрах lo и hi значения рабочих (run-time) переменных, т. е. не требует известных при компиляции значений. Нет также требования, чтобы значение (hi — lo + 1) было степенью 2. Тип T ограничен bit<W> с W не больше 64.

Реализация хэширования в BMv2

Реализация в BMv2 v1model функции hash поддерживает в параметрах base и max значения рабочих (run-time) переменных, т. е. не требует известных при компиляции значений. Нет также требования, чтобы значение max было степенью 2.

Вызов hash возвращает значение, рассчитанное для данных H. Значением, записываемым в выходной параметр result, будет (base + (H % max)), если max ≥ 1 и base в противном случае. Тип O для result, T для base и M для max ограничены bit<W> с W не больше 64.

Реализация BMv2 direct_counter

Если таблица t имеет объект direct_counter c, связанный с ней, включая в свое определение свойство counters = c, реализация BMv2 v1model ведет себя так, будто каждое действие для этой таблицы содержит в точности один вызов c.count(), независимо от их реального числа.

Реализация BMv2 direct_meter

Если таблица t имеет объект direct_meter m, связанный с ней, включая в свое определение свойство meters = m, хотя бы одно из действий этой таблицы должно включать вызов m.read(result_field); для некоторого поля result_field типа bit<W> с W ≥ 2. Когда это делается, реализация BMv2 v1model ведет себя так, будто все действия этой таблицы имеют такой вызов, независимо от его реального наличия.

Компилятор p4c выдает сообщение об ошибке (и не поддерживает) наличие для таблицы t двух действий, одно из которых имеет m.read(result_field1), а другое — m.read(result_field2) или оба вызова происходят в одном действии. Все вызовы метода read() для m должны иметь один параметр result, куда записывается результат.

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

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

nmalykh@protokols.ru

1Network Interface Card. Прим. перев.

2Portable Switch Architecture.

3Принятое по умолчанию значение 511 для simple_switch может быть изменено опцией командной строки —drop-port в зависимости от платформы.

4Должно выполняться условие lo ≤ hi. При поиске ключ k будет соответствовать, если выполняется условие lo ≤ k ≤ hi.

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

6Должно выполняться условие val == (val & mask) и маска должна быть «префиксной», т. е. иметь непрерывную последовательность 1 в старших битах. При попытке задать префикс как val/prefix_length в программе P416 (синтаксис, применяемый некоторыми командами линейных интерфейсов для задания префикса, например, в simple_switch_CLI), это будет арифметическим выражением для деления val на prefix_length и приведет к отказу в случае val для поиска совпадения. Компилятор не будет выдавать предупреждений, поскольку используется корректный синтаксис операции деления.

7Эквивалент диапазона val .. val и ведет себя как поиск совпадения для val.

8Эквивалент val &&& mask с 1 во всех позициях маски и ведет себя как поиск совпадения для val.

9Соответствует любому разрешенному значению поля. Эквивалент min_posible_field_value .. max_possible_field_value для поля range или 0 &&& 0 для полей ternary и lpm.

 

Рубрика: SDN, Сетевое программирование | Комментарии к записи BMv2 simple_switch отключены

Измерение пропускной способности

NTK_RFC 0002

Bandwidth measurement

Измерение пропускной способности

PDF

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

Проблема измерения качества каналов

В текущей версии Npv7 радар (Radar) измеряет качество канала, используя лишь время кругового обхода rtt1 и потери пакетов. Это явно не оптимально, поскольку пропускная способность не учитывается и канал с малой пропускной способностью (например, 20 Кбит/с), но с небольшой задержкой окажется лучше широкополосного канала с умеренной задержкой.

Улучшение

Узел должен включать в пакеты трассировки (Tracer Packet) не только rtt пройденных каналов, но и текущую их пропускную способность (полосу). Это позволит формировать трафик с учетом реальной пропускной способности каналов.

Измерение пропускной способности

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

Общая доступная полоса

        A <--> B <--> C

Узел B «ловит» трафик между узлами A и C. В конце ловушки B измеряет общую доступную полосу каналов B—>C и B—>A. Имеются разные методы измерения пропускной способности каналов. Первый и простейший заключается в передаче неопределенного числа случайных пакетов в течение нескольких секунд по адресу получателя на канале. Скорость передачи контролируется с помощью libpcap и регистрируется максимальное число переданных в секунду байтов как доступная для выгрузки (upload) полоса данного канала. Этот метод очень эффективен и измеренная полоса является реальной, а не аппроксимированной. Побочным эффектом является полная загрузка канала на несколько секунд.

Другой вариант более изыскан и использует методы Packet Pair и Packet Train, описанные в http://perform.wpi.edu/downloads/wbest/README и http://www-static.cc.gatech.edu/fac/Constantinos.Dovrolis/bw.html.

Поскольку каналы могут быть асимметричными, измерения повторяются также для A—>B и C—>B. В результате будет получена пропускная способность каналов A->B, B->A, C->B, B->C.

Контроль полосы в реальном масштабе времени

С помощью библиотеки libpcap узел B может отслеживать использование полосы каналов.

Max_link_bw

Общая доступная пропускная способность канала.

nflows

Текущее число потоков в канале. Потоком считается множество (stream) пакетов, связанных с конкретным соединением.

Текущую доступную полосу канала можно аппроксимировать выражением

link_avail_bw   = Max_link_bw/(nflows+1)

Такая аппроксимация выбрана потому, что метод управления трафиком TCP похож на алгоритм max-min и при большом числе потоков link_avail_bw является хорошим приближением.

Если радар сообщает о значительных вариациях текущей доступной полосы, передается новый запрос QSPN.

Задержка rtt

Каждый узел сети будет задерживать пересылку полученного пакета Tracer (TP) на время, обратно пропорциональное link_avail_bw. Таким образом, пакеты TP будут приниматься для эффективности. Побочным эффектом этого правила является игнорирование крайних случаев, когда маршруты с очень низким значением rtt будут иметь очень малую полосу bw или маршруты с оптимальной полосой bw будут иметь очень большое значение rtt. Однако в реальном мире такие ситуации достаточно редки, поскольку rtt и зачастую связаны между собой.

Дополнительная информация об эффективности пакетов TP приведена в документе QSPN v2.

Пропускная способность маршрута

Пропускная способность маршрута из S в D обозначается bw(S -> D) и равна пропускной способности наихудшего канала в пути. Например, для маршрута

S --64Mb/s--> R --64Mb/s--> T --32Mb/s--> O --100Mb/s--> I --100Mb/s--> D

пропускная способность составит bw(S -> D) = 32 Мбит/с.

Пакет TP должен запомнить единственное значение пропускной способности (_one_ bw) и это будет bw() текущего маршрута. Например, если S передает TP, то по получении этого пакета узлом T, сохраненная в нем пропускная способность будет bw(S->R->T)=64, но при достижении узла O, она сменится на bw(S→R→T→O)=32.

Отметим, что каждое значение bw приблизительно соответствует доступной полосе соответствующего канала (link_avail_bw). Например, запись S —64Mb/s—> R показывает, что текущая пропускная способность канала S—>R составляет приблизительно 64 Мбит/с.

Асимметричные маршруты

QSPN v1 предоставляет каждому узлу наилучший маршрут загрузки трафика (download) с каждого другого узла. Рассмотрим в качестве примера узлы S и D.

       1:   ,--<-<-<---.
           /            \
         S                D
           \            / 
       2:   `-->->->---'

Маршруты типа 1 являются лучшими маршрутами выгрузки (upload) из D в S, а маршруты типа 2 — лучшими для противоположного направления. QSPN дает S только маршруты типа 1, а D знает только маршруты типа 2. Если маршруты не симметричны, S при выгрузке в D будет использовать маршрут типа 1, который может иметь низкую производительность.

Решение очень просто — S при выгрузке большого блока данных будет запрашивать у D его маршруты D->S (тип 2), т. е. лучшие маршруты выгрузки из S в D.

Демон Netsukuku, используя libpcap, будет отслеживать пакеты, исходящие от localhost. Если пакеты отправлялись более 16 секунд, демон будет запрашивать у целевого узла лучшие маршруты выгрузки (upload) и добавлять их в свою таблицу маршрутизации. Таким образом, маршруты выгрузки будут запрашиваться лишь при необходимости.

Отметим, что в QSPN v2 имеется встроенная поддержка асимметричной маршрутизации.

Задержка и полоса

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

Узлам Netsukuku следует поддерживать три типа таблиц маршрутизации:

  • маршруты с наилучшей пропускной способностью;

  • маршруты с наименьшей задержкой;

  • маршруты с эффективной комбинацией пропускной способности и задержки.

Если протокол приложения использует значения TOS в своих пакетах IP, можно добавить правила маршрутизации и выбрать для протокола подходящую таблицу (например, вторую для ssh).

Возможен вариант хранения в ядре лишь усредненной (третьей) таблицы, поскольку она применяется наиболее часто. Конкретные маршруты с наилучшей пропускной способностью и задержкой можно добавлять в ядро лишь в момент их использования. Например, при запуске bittorrent для загрузки с узлов A,B,C монитор ntkd будет добавлять в ядро маршруты с наилучшей пропускной способностью для A,B,C.

IGS

Шлюз inet-gw, позволяющий совместно использовать свое соединение с Internet, замеряет свою доступную полосу Internet-соединения. Максимальная пропускная способность в обоих направлениях известна, поскольку пользователь должен задать ее в конфигурационном файле netsukuku.conf. Таким образом, отслеживание устройства, используемого принятым по умолчанию маршрутом в Internet, позволяет легко узнать доступную полосу.

Балансировка нагрузки

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

Балансировка нагрузки внутри Netsukuku не зависит от проблем балансировки на IGS. Узлы ntk согласованы между собой и не используют NAT для взаимодействия внутри сети.


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

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

nmalykh@protokols.ru

1Round-trip time.

Рубрика: Фрактальные сети | Комментарии к записи Измерение пропускной способности отключены

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

PDF

Компоненты библиотеки

Judy1 отображает индекс (Index — word) на бит.

JudyL отображает индекс (Index — word) на Value (word/указатель).

JudySL отображает индекс (строка с null-завершением) на Value.

JudyHS отображает индекс (массив байтов) размером Length на Value.

Judy представляет собой библиотеку C для создания и использования динамических массивов. Библиотека была предложена и реализована Doug Baskins из Hewlett-Packard.

Семейство функций и макросов поддерживает полностью динамические массивы, которые могут индексироваться 32- или 64-битовыми словами (в зависимости от размера word для процессора), строками с null-символом в конце или массивом байтов заданного размера. Динамический массив (разреженный) можно также считать функцией отображения или ассоциативной памятью.

Тип Word_t определен как typedef unsigned long int в файле Judy.h и должен иметь размер sizeof(void *), т. е. указателя.

В функциях Judy1 Index имеет тип Word_t, а Value — просто бит (bit) или флаг наличия (отсутствия) Index в массиве. Этот можно рассматривать как огромное битовое поле (huge bitmap).

В функциях JudyL Index и Value имеют тип Word_t. В результате JudyL является чистым отображением слова на слово (указатель). JudySL и JudyHL базируются на этом свойстве JudyL.

В функциях JudySL Index является строкой с null-символом в конце, а ValueWord_t.

В функциях JudyHS Index является массивом байтов размера Length, а Value имеет тип Word_t. Дополнение (май 2004 г.) в Judy является гибридом, в котором используются лучшие качества методов хэширования и Judy. Автор надеется, что JudyHS послужит хорошей заменой методов хэширования, когда размер хэш-таблицы меняется при росте численности. Корректно настроенный метод хеширования со статическим размером таблицы и численностью непобедим по скорости. Однако JudyHS будет работать лучше с численностью, отличающейся от оптимального размера хэш-таблицы. JudyHS не теряет производительности в тех случаях, где это известно для алгоритмов хэширования. JudyHS не использует связный список для обработки конфликтов хэш-значений, применяя взамен дерево массивов JudyL и виртуальную хэш-таблицу размером 4 миллиарда.

Массивы Judy являются быстрыми и небольшими по памяти, не требуют настройки для широкого диапазона типов набора индексов (последовательные, периодические, кластерные, случайные). Judy обычно превосходит по скорости и экономии памяти другие модели хранения данных, такие как skip-списки, связные списки, двоичные, троичные и b-деревья и даже хэширование, а также повышает эффективность работы при очень больших размерах данных.

Массив Judy создается просто путем определения пустого (null) указателя, а затем по этому указателю сохраняется (вставляется) первый элемент массива. Используемая массивом Judy память примерно пропорциональна численности (количеству элементов).

Judy имеет два интерфейса API — макросы C и вызовы функций. Поскольку макросы иногда быстрее и имеют более простой интерфейс обработки ошибок по сравнению с эквивалентными функциями, они являются предпочтительным вариантом.

Поскольку исходный (пустой) массив Judy представляется null-указателем, можно создать массив массивов Judy. Т. е. значениями массива Judy (Value), кроме Judy1, могут быть указатели на другие массивы Judy. Это существенно упрощает создание массивов произвольной размерности или значения Index (массивы JudySL и JudyHS используют для этого JudyL).

Файлы

/usr/share/doc/Judy/ — страницы руководства в формате HTML;
/usr/share/doc/Judy/demo/ — примеры файлов исходного кода.

Ошибки

На упрощение обработки ошибок в Judy было затрачено много сил и времени, что позволило сохранить гибкость и функциональность. Тема обработки ошибок достаточно скучна, но упускать ее нельзя. Из описанных здесь методов рекомендуется применять второй. Он создает наиболее быстрый код, используя минимальный объем памяти и требует написания дополнительного кода лишь для функций вставки и удаления. Метод совместим с двумя другими и предназначен для создания кода, который может обрабатывать отказы malloc() иначе, чем принято по умолчанию в Judy. Если принятый по умолчанию метод Judy работает хорошо, имеет смысл применять его.

Имеется две категории ошибок, возвращаемых Judy (и другими динамическими ADT):

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

  2. Нехватка памяти (отказ malloc()) при вставке (Insert, Set) или удалении (Delete, Unset) для изменения массива Judy. Для вставки или удаления не всегда применяется функция malloc(), поэтому операции могут быть успешными даже при отказе malloc().

Имеется три метода обработки ошибок при использовании макросов

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

File 'YourCfile.c', line 1234: JudyLIns(), JU_ERRNO_* == 2, ID == 321

Это говорит об ошибке в строке 321 функции JudyLIns(). Проблема возникла в строке 1234 файла YourCfile.c, где произошел отказ JLI(). JU_ERRNO_* == 2 указывает JU_ERRNO_NOMEM (определена в файле Judy.h). Идентификатор ID указывает номер строки исходного файла, где произошла ошибка. Программа была прервана с возвратом exit(1);. По умолчанию обе категории ошибок Judy обрабатываются этим методом.

  1. Запрет обработки ошибок в макросах

    Когда в программе «нет ошибок», сообщения могут выдаваться лишь для отказов malloc() и все возвращаемые ошибки можно считать такими отказами. Используя показанный ниже оператор #define, можно отключить все проверки и вывод ошибок. К коду, который может приводить к отказам malloc(), потребуется добавить специальный код. Judy сохраняет в массиве данные, присутствовавшие до вызова, в случаях отказа malloc()1.

#define JUDYERROR_NOTEST 1

в программном коде или

cc -DJUDYERROR_NOTEST sourcefile -lJudy

в командной строке.

//Пример использования метода 2 в программе.
    
JLI(PValue, PLArray, Index);
if (PValue == PJERR) goto out_of_memory_handling;
...

JLD(RC_int, PLArray, Index);
if (RC_int == JERR) goto out_of_memory_handling;
...

J1S(RC_int, P1Array, Index);
if (RC_int == JERR) goto out_of_memory_handling;
...

J1U(RC_int, P1Array, Index);
if (RC_int == JERR) goto out_of_memory_handling;
...

Отметим, что без определения JUDYERROR_NOTEST операция goto out_of_memory_handling не будет выполняться и компилятор исключит ее при оптимизации. Будет использован принятый по умолчанию метод использования макроса для вывода сообщения об ошибке, как описано выше. При определении JUDYERROR_NOTEST операция goto out_of_memory_handling будет выполняться в случае возникновения ошибки (отказ malloc()).

  1. Заданный пользователем макрос JUDYERROR()

    Макрос JUDYERROR(), определенный в файле Judy.h обеспечивает гибкую обработку ошибок, позволяющую программе использовать макросы массивов Judy вместо вызова функций. Можно использовать свой макрос JUDYERROR(), соответствующий реальным задачам. Ниже приведен пример возможного варианта взамен принятого по умолчанию. Он служит для различения двух типов ошибок и явно1 проверки остальных ошибок JU_ERRNO_NOMEM, которые могут возникать в программе.

// Пример Judy macro API для продолжения работы при нехватке памяти,
// вывода сообщения и возврата exit(1) при возникновении ошибки.

#ifndef JUDYERROR_NOTEST
#include <stdio.h>  // нужен для fprintf()

// Макрос, используемый Judy macro API для возврата кода -1:

#define JUDYERROR(CallerFile, CallerLine, JudyFunc, JudyErrno, JudyErrID) \
{                                                                         \
    if ((JudyErrno) != JU_ERRNO_NOMEM) /* не отказ malloc() */         	  \
    {                                                                     \
        (void) fprintf(stderr, "File '%s', line %d: %s(), "               \
            "JU_ERRNO_* == %d, ID == %d\n",                               \
            CallerFile, CallerLine,                                       \
            JudyFunc, JudyErrno, JudyErrID);                              \
        exit(1);                                                          \
    }                                                                     \
}
#endif // не определено JUDYERROR_NOTEST

Этот макрос обработки ошибок должен включаться в программу до оператора #include <Judy.h>.

Judy1

Макросы Judy1

Макросы Judy1 представляют собой библиотеку C для создания и использования динамического массива битов. Индексом массива может служить любое значение слова (word).

Массив Judy1 эквивалентен массиву или отображению (map) битов, адресуемому Index (ключ). Массив может быть разреженным (sparse), а Index может быть любым значением (Value) размера word. Наличие индекса представляет установленный (set) бит, а такой бит представляет наличие индекса. Отсутствие индекса представляет сброшенный (unset) бит, а сброшенный бит представляет отсутствие индекса.

Массив Judy1 выделяется с указателем NULL

Pvoid_t PJ1Array = (Pvoid_t) NULL;

Память для поддержки массива выделяется при установке бита и освобождается при его сбросе. Если указатель Judy1 (PJ1Array) имеет значение NULL, это говорит, что все биты сброшены (для массива Judy1 не требуется памяти).

Как и обычные массивы Judy1, не имеет дубликатов индексов.

При использовании описанных здесь макросов вместо вызова функций Judy1 принятая по умолчанию обработка ошибок передает сообщение на стандартный вывод ошибок и завершает программу с кодом exit(1). Другие методы обработки ошибок рассмотрены в разделе Ошибки.

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

Синтаксис

cc [flags] sourcefiles -lJudy
#include <Judy.h>

int     Rc_int;                          // Код возврата - integer
Word_t  Rc_word;                         // Код возврата - unsigned word
Word_t  Index, Index1, Index2, Nth;

Pvoid_t PJ1Array = (Pvoid_t) NULL;       // Инициализация массива Judy1

J1S( Rc_int,  PJ1Array, Index);          // Judy1Set()
J1U( Rc_int,  PJ1Array, Index);          // Judy1Unset()
J1T( Rc_int,  PJ1Array, Index);          // Judy1Test()
J1C( Rc_word, PJ1Array, Index1, Index2); // Judy1Count()
J1BC(Rc_int,  PJ1Array, Nth, Index);     // Judy1ByCount()
J1FA(Rc_word, PJ1Array);                 // Judy1FreeArray()
J1MU(Rc_word, PJ1Array);                 // Judy1MemUsed()
J1F( Rc_int,  PJ1Array, Index);          // Judy1First()
J1N( Rc_int,  PJ1Array, Index);          // Judy1Next()
J1L( Rc_int,  PJ1Array, Index);          // Judy1Last()
J1P( Rc_int,  PJ1Array, Index);          // Judy1Prev()
J1FE(Rc_int,  PJ1Array, Index);          // Judy1FirstEmpty()
J1NE(Rc_int,  PJ1Array, Index);          // Judy1NextEmpty()
J1LE(Rc_int,  PJ1Array, Index);          // Judy1LastEmpty()
J1PE(Rc_int,  PJ1Array, Index);          // Judy1PrevEmpty()

Определения

J1S(Rc_int, PJ1Array, Index); // Judy1Set()

Устанавливает бит Index в массиве Judy1 PJ1Array. Возвращает Rc_int = 1 при сброшенном бите Index (нет ошибки) и 0 при установленном ранее бите (ошибка).

J1U(Rc_int, PJ1Array, Index); // Judy1Unset()

Сбрасывает бит Index в массиве Judy1 PJ1Array. Возвращает Rc_int = 1 при установленном ранее бите Index (нет ошибки) и 0 при сброшенном бите (ошибка).

J1T(Rc_int, PJ1Array, Index); // Judy1Test()

Проверяет установку бита Index в массиве Judy1 PJ1Array. Возвращает Rc_int = 1 при установленном бите Index (Index присутствует) и 0 при сброшенном бите (Index отсутствует).

J1C(Rc_word, PJ1Array, Index1, Index2); // Judy1Count()

Считает число индексов, присутствующих в массиве Judy1 PJ1Array между значениями Index1 и Index2 (включительно). Возвращает в Rc_word число индексов. Возврат Value = 0 может быть корректным для счетчика или указывать особый случай полностью заполненного массива (только на 32-битовых машинах). Более подробно это рассмотрено в описании функции Judy1Count(). Для подсчета числа присутствующих в массиве Judy1 индексов (численность) служит

J1C(Rc_word, PJ1Array, 0, -1);

Отметим, что -1 указывает максимальный индекс (все 1).

J1BC(Rc_int, PJ1Array, Nth, Index); // Judy1ByCount()

Находит N-й индекс, который присутствует в массиве Judy1 PJ1Array (Nth = 1 возвращает первый имеющийся индекс). Для указания последнего индекса в полностью заполненном массиве (присутствуют все индексы, что бывает редко) используется Nth = 0. Возвращает Rc_int = 1 и Index = Nth, если индекс найден, и Rc_int = 0 в противном случае (Value в Index не содержит полезной информации).

J1FA(Rc_word, PJ1Array); // Judy1FreeArray()

Освобождает весь массив Judy1 PJ1Array (быстрее цикла J1N(), J1U()). Возвращает в Rc_word число освобожденных битов и устанавливает PJ1Array = NULL.

J1MU(Rc_word, PJ1Array); // Judy1MemUsed()

Возвращает в Rc_word число байтов памяти, занятых массивом Judy1 PJ1Array. Это очень быстрый макрос и его можно применять после J1S() или J1U() практически без снижения производительности.

Макросы поиска Judy1

Средства поиска в Judy1 позволяют найти установленные или сброшенные биты в массиве. Поиск может быть включающим или исключающим и выполняется в обоих направлениях. Все средства поиска используют похожие последовательности вызова. При успешном поиске в Rc_int возвращается 1 вместе с найденным индексом Index. При неудачном поиске Rc_int = 0, а Index не содержит полезной информации. Код возврата Rc_int должен проверяться до использования возвращенного Index, поскольку при поиске возможны отказы.

J1F(Rc_int, PJ1Array, Index); // Judy1First()

Включительный поиск первого присутствия индекса не меньше указанного Index. Для поиска первого элемента служит Index = 0. J1F() обычно служит для начала упорядоченного сканирования индексов в массиве Judy1.

J1N(Rc_int, PJ1Array, Index); // Judy1Next()

Исключительный поиск первого присутствия индекса больше указанного Index. J1N() обычно служит для продолжения упорядоченного сканирования индексов в массиве Judy1 или поиска «соседа» указанного индекса.

J1L(Rc_int, PJ1Array, Index); // Judy1Last()

Включительный поиск последнего присутствия индекса не больше указанного Index. Для поиска последнего индекса в массиве используется Index = -1 (все 1). J1L() обычно служит для начала упорядоченного сканирования в обратном направлении индексов в массиве Judy1.

J1P(Rc_int, PJ1Array, Index); // Judy1Prev()

Исключительный поиск предыдущего индекса меньше указанного Index. J1P() обычно служит для продолжения упорядоченного сканирования в обратном направлении индексов в массиве Judy1 или поиска «соседа» указанного индекса.

J1FE(Rc_int, PJ1Array, Index); // Judy1FirstEmpty()

Включительный поиск первого отсутствия индекса не меньше указанного Index. Для поиска первого отсутствующего элемента служит Index = 0.

J1NE(Rc_int, PJ1Array, Index); // Judy1NextEmpty()

Исключительный поиск первого отсутствия индекса больше указанного Index.

J1LE(Rc_int, PJ1Array, Index); // Judy1LastEmpty()

Включительный поиск последнего присутствия индекса не больше указанного Index. Для поиска последнего отсутствующего индекса в массиве используется Index = -1 (все 1)

J1PE(Rc_int, PJ1Array, Index); // Judy1PrevEmpty()

Исключительный поиск предыдущего индекса меньше указанного Index.

Пример

В приведенном ниже примере ошибки при вызове J1S() или J1U() вызывают пользовательскую процедуру process_malloc_failure. Этого не требуется при использовании принятого по умолчанию макроса JUDYERROR(), поскольку он завершает программу при любом отказе, включая ошибки malloc().

#include <stdio.h>
#include <Judy.h>

int main()  {                    // Пример программы с Judy1 macro API
   Word_t Index;                 // индекс (или ключ)
   Word_t Rcount;                // счетчик индексов (установленных битов)
   Word_t Rc_word;               // возвращаемое значение (word)
   int    Rc_int;                // возвращаемое логическое значение (0 или 1)

   Pvoid_t PJ1Array = (Pvoid_t) NULL; // инициализация массива Judy1

   Index = 123456;
   J1S(Rc_int, J1Array, Index);  // Установка бита 123456
   if (Rc_int == JERR) goto process_malloc_failure;
   if (Rc_int == 1) printf("OK - bit successfully set at %lu\n", Index);
   if (Rc_int == 0) printf("BUG - bit already set at %lu\n", Index);

   Index = 654321;
   J1T(Rc_int, J1Array, Index);  // проверка установки бита 654321
   if (Rc_int == 1) printf("BUG - set bit at %lu\n", Index);
   if (Rc_int == 0) printf("OK - bit not set at %lu\n", Index);

   J1C(Rcount, J1Array, 0, -1);  // счетчик установленных битов массива
   printf("%lu bits set in Judy1 array\n", Rcount);

   Index = 0;
   J1F(Rc_int, J1Array, Index);  // найти первый бит, установленный в массиве
   if (Rc_int == 1) printf("OK - first bit set is at %lu\n", Index);
   if (Rc_int == 0) printf("BUG - no bits set in array\n");

   J1MU(Rc_word, J1Array);       // объем используемой памяти
   printf("%lu Indexes used %lu bytes of memory\n", Rcount, Rc_word);

   Index = 123456;
   J1U(Rc_int, J1Array, Index);  // сбросить бит 123456
   if (Rc_int == JERR) goto process_malloc_failure;
   if (Rc_int == 1) printf("OK - bit successfully unset at %lu\n", Index);
   if (Rc_int == 0) printf("BUG - bit was not set at %lu\n", Index);

   return(0);
}

Функции Judy1

Функции Judy1 обеспечивают библиотеку C для работы с динамическими массивами битов, в которых индексом служит любое значение типа word.

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

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

При вызове функций Judy1 используется дополнительный параметр по сравнению с соответствующими макросами. Этим параметром является указатель на структуру ошибки или NULL (если детали ошибок не возвращаются).

Ниже функции описаны в терминах их использования макросами (для случая #define JUDYERROR_NOTEST 1). Это является рекомендуемым использованием макросов после завершения отладки программ. Если макрос JUDYERROR_NOTEST не задан, объявляется структура ошибки для хранения информации, возвращаемой из функций Judy1 при возникновении ошибок.

Следует обращать внимание на размещение символа & в разных функциях.

Синтаксис

int    Judy1Set(PPvoid_t PPJ1Array, Word_t Index, PJError_t PJError);
int    Judy1Unset(PPvoid_t PPJ1Array, Word_t Index, PJError_t PJError);
int    Judy1Test(Pcvoid_t PJ1Array, Word_t Index, PJError_t PJError);
Word_t Judy1Count(Pcvoid_t PJ1Array, Word_t Index1, Word_t Index2, PJError_t PJError);
int    Judy1ByCount(Pcvoid_t PJ1Array, Word_t Nth, Word_t * PIndex, PJError_t PJError);
Word_t Judy1FreeArray(PPvoid_t PPJ1Array, PJError_t PJError);
Word_t Judy1MemUsed(Pcvoid_t PJ1Array);
int    Judy1First(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);
int    Judy1Next(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);
int    Judy1Last(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);
int    Judy1Prev(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);
int    Judy1FirstEmpty(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);
int    Judy1NextEmpty(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);
int    Judy1LastEmpty(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);
int    Judy1PrevEmpty(Pcvoid_t PJ1Array, Word_t * PIndex, PJError_t PJError);

Определения

Judy1Set(&PJ1Array, Index, &JError)

#define J1S(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1Set(&PJ1Array, Index, PJE0)

Judy1Unset(&PJ1Array, Index, &JError)

#define J1U(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1Unset(&PJ1Array, Index, PJE0)

Judy1Test(PJ1Array, Index, &JError)

#define J1T(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1Test(PJ1Array, Index, PJE0)

Judy1Count(PJ1Array, Index1, Index2, &JError)

#define J1C(Rc_word, PJ1Array, Index1, Index2) \
   Rc_word = Judy1Count(PJ1Array, Index1, Index2, PJE0)

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

JError_t JError;
Rc_word = Judy1Count(PJ1Array, Index1, Index2, &JError);
if (Rc_word == 0)	{
    if (JU_ERRNO(&JError) == JU_ERRNO_NONE)
        printf("Judy1 array population == 0\n");
    if (JU_ERRNO(&JError) == JU_ERRNO_FULL)
        printf("Judy1 array population == 2^32\n");
    if (JU_ERRNO(&JError) == JU_ERRNO_NULLPPARRAY)
        goto NullArray;
    if (JU_ERRNO(&JError) >  JU_ERRNO_NFMAX)
        goto Null_or_CorruptArray;
}

Judy1ByCount(PJ1Array, Nth, &Index, &JError)

#define J1BC(Rc_int, PJ1Array, Nth, Index) \
   Rc_int = Judy1ByCount(PJ1Array, Nth, &Index, PJE0)

Judy1FreeArray(&PJ1Array, &JError)

#define J1FA(Rc_word, PJ1Array) \
   Rc_word = Judy1FreeArray(&PJ1Array, PJE0)

Judy1MemUsed(PJ1Array)

#define J1MU(Rc_word, PJ1Array) \
   Rc_word = Judy1MemUsed(PJ1Array)

Judy1First(PJ1Array, &Index, &JError)

#define J1F(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1First(PJ1Array, &Index, PJE0)

Judy1Next(PJ1Array, &Index, &JError)

#define J1N(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1Next(PJ1Array, &Index, PJE0)

Judy1Last(PJ1Array, &Index, &JError)

#define J1L(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1Last(PJ1Array, &Index, PJE0)

Judy1Prev(PJ1Array, &Index, &JError)

#define J1P(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1Prev(PJ1Array, &Index, PJE0)

Judy1FirstEmpty(PJ1Array, &Index, &JError)

#define J1FE(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1FirstEmpty(PJ1Array, &Index, PJE0)

Judy1NextEmpty(PJ1Array, &Index, &JError)

#define J1NE(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1NextEmpty(PJ1Array, &Index, PJE0)

Judy1LastEmpty(PJ1Array, &Index, &JError)

#define J1LE(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1LastEmpty(PJ1Array, &Index, PJE0)

Judy1PrevEmpty(PJ1Array, &Index, &JError)

#define J1PE(Rc_int, PJ1Array, Index) \
   Rc_int = Judy1PrevEmpty(PJ1Array, &Index, PJE0)

Определения всех функций Judy, типов Pvoid_t, Pcvoid_t, PPvoid_t, Word_t, JError_t и PJError_t, констант NULL, JU_ERRNO_*, JERR и PJE0 приведены в файле Judy.h (/usr/include/Judy.h). Следует отметить, что вызывающие должны определять массивы Judy1 типа Pvoid_t, который можно передать функциям, принимающим Pcvoid_t (constant Pvoid_t), а также по адресу в функции, принимающие PPvoid_t.

JudyL

Макросы JudyL

Макросы JudyL представляют собой библиотеку C для создания и использования динамического массива слов (word). Индексом массива может служить любое значение слова (word).

Массив JudyL эквивалентен массиву значений Value типа word, адресуемому Index (ключ). Массив может быть разреженным (sparse), а Index может быть любым значением (Value) размера word. Память для поддержки массива выделяется при вставке пары «индекс-значение» и освобождается при ее удалении. Массив JudyL можно также считать отображением слова на другое слово (указатель).

Если указатель Judy1 (PJ1Array) имеет значение NULL, это говорит, что все биты сброшены (для массива Judy1 не требуется памяти).

Как и обычные массивы, JudyL не имеет дубликатов индексов.

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

Массив JudyL выделяется с указателем NULL

Pvoid_t PJLArray = (Pvoid_t) NULL;

При использовании описанных здесь макросов вместо вызова функций JudyL принятая по умолчанию обработка ошибок передает сообщение на стандартный вывод ошибок и завершает программу с кодом exit(1). Другие методы обработки ошибок рассмотрены в разделе Ошибки.

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

Синтаксис

cc [flags] sourcefiles -lJudy

#include <Judy.h>

int     Rc_int;                          // Код возврата - integer
Word_t  Rc_word;                         // Код возврата - unsigned word
Word_t  Index, Index1, Index2, Nth;

PWord_t  PValue;                         // Указатель на возвращаемое значение
Pvoid_t PJLArray = (Pvoid_t) NULL;       // Инициализация массива JudyL

JLI( PValue,  PJLArray, Index);          // JudyLIns()
JLD( Rc_int,  PJLArray, Index);          // JudyLDel()
JLG( PValue,  PJLArray, Index);          // JudyLGet()
JLC( Rc_word, PJLArray, Index1, Index2); // JudyLCount()
JLBC(PValue,  PJLArray, Nth, Index);     // JudyLByCount()
JLFA(Rc_word, PJLArray);                 // JudyLFreeArray()
JLMU(Rc_word, PJLArray);                 // JudyLMemUsed()
JLF( PValue,  PJLArray, Index);          // JudyLFirst()
JLN( PValue,  PJLArray, Index);          // JudyLNext()
JLL( PValue,  PJLArray, Index);          // JudyLLast()
JLP( PValue,  PJLArray, Index);          // JudyLPrev()
JLFE(Rc_int,  PJLArray, Index);          // JudyLFirstEmpty()
JLNE(Rc_int,  PJLArray, Index);          // JudyLNextEmpty()
JLLE(Rc_int,  PJLArray, Index);          // JudyLLastEmpty()
JLPE(Rc_int,  PJLArray, Index);          // JudyLPrevEmpty()

JLI(PValue, PJLArray, Index) // JudyLIns()

Вставляет Index и Value в массив JudyL PJLArray. При успешной вставке устанавливается значение Value = 0. Если Index уже присутствует, Value не меняется. Возвращает указатель PValue на значение Value, который программа может использовать для чтения или изменения Value, пока не будет выполнен следующий макрос JLI() (вставка), JLD() (удаление) или JLFA() (очистка массива) для массива PJLArray. Например,

*PValue = 1234;
Value = *PValue;

Возвращает в PValue значение PJERR, если возникает отказ malloc(). Отметим, что макросы JLI() и JLD() реорганизуют массив JudyL, поэтому PValue из предыдущего вызова JudyL становится непригодным и должно быть обновлено.

JLD(Rc_int, PJLArray, Index) // JudyLDel()

Удаляет пару IndexValue из массива JudyL. Макрос возвращает Rc_int = 1 при успешном удалении и Rc_int = 0, если Index отсутствует. При отказе malloc() устанавливается Rc_int = JERR.

JLG(PValue, PJLArray, Index) // JudyLGet()

Возвращает указатель PValue, связанный с Index в массиве PJLArray. Макрос возвращает значение Pvalue, указывающее на Value. При отсутствии Index возвращается PValue = NULL, а пр отказе malloc() — PValue = PJERR.

JLC(Rc_word, PJLArray, Index1, Index2) // JudyLCount()

Возвращает число индексов, имеющихся в массиве JudyL PJLArray между значениями Index1 и Index2 (включительно). Rc_word содержит значение счетчика. Значение 0 может указывать отсутствие индексов. Для подсчета всех индексов в массиве JudyL используется вызов

JLC(Rc_word, PJLArray, 0, -1);

JLBC(PValue, PJLArray, Nth, Index) // JudyLByCount()

Находит N-й, присутствующий в массиве JudyL PJLArray (при Nth = 1 возвращается первый имеющийся индекс).

Возвращает PValue с указанием на Value и Index, установленные для имеющегося N-ого индекса или PValue = NULL, если индекс отсутствует (значение Index в этом случае не имеет смысла).

JLFA(Rc_word, PJLArray) // JudyLFreeArray()

По указателю на массив JudyL полностью освобождает его гораздо быстрее чем в цикле JLN(), JLD(). Возвращает в Rc_word число освобожденных байтов и устанавливает PJLArray = NULL.

JLMU(Rc_word, PJLArray) // JudyLMemUsed()

Возвращает в Rc_word число байтов, выделенных malloc() для PJLArray. Это очень быстрый макрос и его можно использовать перед или после JLI() или JLD() практически без влияния на производительность.

Средства поиска JudyL

Макросы JLF(), JLN(), JLL(), JLP() позволяют выполнять поиск в массиве. Поиск может быть включительным или исключительным и выполняться в обоих направлениях. При успешном поиске Index содержит найденный индекс, а PValue — указатель на значение (Value) для найденного Index. Если поиск не дал результата, возвращается PValue = NULL, а Index не содержит полезной информации. Указатель PValue должен проверяться до использования Index, поскольку при поиске возможны отказы.

Макросы JLFE(), JLNE(), JLLE(), JLPE() позволяют искать отсутствующие (пустые) индексы в массиве. Поиск может быть включительным или исключительным и выполняется в обоих направлениях. При успешном поиске возвращается Index отсутствующего индекса и Rc_int = 1. При неудаче возвращается Rc_int = 0, а Index не содержит полезной информации. Значение Rc_int должно проверяться до использования Index, поскольку при поиске возможен отказ.

JLF(PValue, PJLArray, Index) // JudyLFirst()

Включительный поиск первого присутствующего индекса не меньше переданного Index. При Index = 0 отыскивается первый имеющийся индекс. JLF() обычно применяется для начала упорядоченного сканирования имеющихся в JudyL индексов.

JLN(PValue, PJLArray, Index) // JudyLNext()

Исключительный поиск первого присутствующего индекса больше переданного Index. JLN() обычно служит для продолжения упорядоченного сканирования индекса JudyL или поиска «соседа» данного индекса.

JLL(PValue, PJLArray, Index) // JudyLLast()

Включительный поиск последнего присутствующего индекса не больше переданного Index. При Index = -1 (все 1) отыскивается последний индекс массива. JLL() обычно применяется для упорядоченного сканирования присутствующих в массиве JudyL индексов в обратном направлении.

JLP(PValue, PJLArray, Index) // JudyLPrev()

Исключительный поиск предыдущего индекса меньше переданного Index. JLP() обычно служит для продолжения упорядоченного сканирования присутствующих в массиве JudyL индексов в обратном направлении.

JLFE(Rc_int, PJLArray, Index) // JudyLFirstEmpty()

Включительный поиск первого отсутствующего индекса не меньше переданного Index. Index = 0 задает поиск первого отсутствующего в массиве индекса.

JLNE(Rc_int, PJLArray, Index) // JudyLNextEmpty()

Исключительный поиск следующего отсутствующего индекса больше переданного Index.

JLLE(Rc_int, PJLArray, Index) // JudyLLastEmpty()

Включительный поиск последнего отсутствующего индекса не больше переданного Index. При Index = -1 (все 1) отыскивается последний индекс, отсутствующий в массиве.

JLPE(Rc_int, PJLArray, Index) // JudyLPrevEmpty()

Исключительный поиск предыдущего индекса меньше переданного Index.

Многомерные массивы JudyL

Запись указателя на другой массив JudyL в Value массива JudyL обеспечивает простой способ поддержки динамических многомерных массивов. Такие массивы (деревья), созданные с помощью JudyL, являются очень быстрыми и нетребовательными к памяти (фактически, так реализованы JudySL и JudyHS). Таким способом можно реализовать произвольную размерность. Для завершения числа измерений (дерева), указатель в Value помечается как не указывающий на другой массив Judy с помощью флага JLAP_INVALID в младших битах указателя2. После удаления флага JLAP_INVALID значение используется как указатель на пользовательские данные. Флаг JLAP_INVALID определен в файле Judy.h.

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

PValue = (PWord_t)PMultiDimArray;

for (Dim = 0; ;Dim++)	{
   if (PValue == (PWord_t)NULL) goto IndexNotFound;

   /* Переход к следующему измерению в массиве. */
   JLG(PValue, (Pvoid_t)*PValue, Index[Dim]);

   /* Проверка указателя на пользовательский буфер. */
   if (*PValue & JLAP_INVALID)) break;
}
UPointer = (UPointer_t) (*PValue & ~JLAP_INVALID);  // маскирование.
printf("User object pointer is 0x%lx\n", (Word_t) UPointer);
       ...

Этот код работает, поскольку malloc() гарантирует возврат указателя с младшими битами, имеющими значение 0x0. Флаг JLAP_INVALID должен удаляться перед использованием указателя.

Пример

Считывание последовательности пар «индекс-значение» со стандартного ввода, сохранение в массиве и вывод в отсортированном виде.

#include <stdio.h>
#include <Judy.h>

Word_t   Index;                     // индекс массива
Word_t   Value;                     // значение элемента массива
Word_t * PValue;                    // указатель на значение элемента массива
int      Rc_int;                    // код возврата

Pvoid_t  PJLArray = (Pvoid_t) NULL; // Инициализация массива JudyL

while (scanf("%lu %lu", &Index, &Value))	{
    JLI(PValue, PJLArray, Index);
    If (PValue == PJERR) goto process_malloc_failure;
    *PValue = Value;                // запись нового значения
}
// Затем перебираются все сохраненные индексы в порядке сортировки сначала по возрастанию, 
// потом по убыванию и во втором проходе каждый индекс удаляется.

Index = 0;
JLF(PValue, PJLArray, Index);
while (PValue != NULL)	{
    printf("%lu %lu\n", Index, *PValue));
    JLN(PValue, PJLArray, Index);
}

Index = -1;
JLL(PValue, PJLArray, Index);
while (PValue != NULL)	{
    printf("%lu %lu\n", Index, *PValue));

    JLD(Rc_int, PJLArray, Index);
    if (Rc_int == JERR) goto process_malloc_failure;

    JLP(PValue, PJLArray, Index);
}

Функции JudyL

Функции JudyL обеспечивают библиотеку C для создания и работы с динамическими массивами слов (word), использующими в качестве индекса любые значения слов (word).

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

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

При вызове функций JudyL используется дополнительный параметр по сравнению с соответствующими макросами. Этим параметром является указатель на структуру ошибки или NULL (если детали ошибок не возвращаются).

Ниже функции описаны в терминах их использования макросами (для случая #define JUDYERROR_NOTEST 1). Это является рекомендуемым использованием макросов после завершения отладки программ. Если макрос JUDYERROR_NOTEST не задан, объявляется структура ошибки для хранения информации, возвращаемой из функций JudyL при возникновении ошибок.

Следует обращать внимание на размещение символа & в разных функциях.

Синтаксис

PPvoid_t JudyLIns(PPvoid_t PPJLArray, Word_t    Index, PJError_t PJError);
int      JudyLDel(PPvoid_t PPJLArray, Word_t    Index, PJError_t PJError);
PPvoid_t JudyLGet(Pcvoid_t  PJLArray, Word_t    Index, PJError_t PJError);
Word_t   JudyLCount(Pcvoid_t PJLArray, Word_t Index1, Word_t Index2, PJError_t PJError);
PPvoid_t JudyLByCount(Pcvoid_t PJLArray, Word_t Nth, Word_t * PIndex, PJError_t PJError);
Word_t   JudyLFreeArray(PPvoid_t PPJLArray, PJError_t PJError);
Word_t   JudyLMemUsed(Pcvoid_t  PJLArray);
PPvoid_t JudyLFirst(Pcvoid_t  PJLArray, Word_t * PIndex, PJError_t PJError);
PPvoid_t JudyLNext(Pcvoid_t  PJLArray, Word_t * PIndex, PJError_t PJError);
PPvoid_t JudyLLast(Pcvoid_t  PJLArray, Word_t * PIndex, PJError_t PJError);
PPvoid_t JudyLPrev(Pcvoid_t  PJLArray, Word_t * PIndex, PJError_t PJError);
int      JudyLFirstEmpty(Pcvoid_t  PJLArray, Word_t * PIndex, PJError_t PJError);
int      JudyLNextEmpty(Pcvoid_t PJLArray, Word_t * PIndex, PJError_t PJError);
int      JudyLLastEmpty(Pcvoid_t PJLArray, Word_t * PIndex, PJError_t PJError);
int      JudyLPrevEmpty(Pcvoid_t PJLArray, Word_t * PIndex, PJError_t PJError);

Определения

JudyLIns(&PJLArray, Index, &JError)

#define JLI(PValue, PJLArray, Index)  \
   PValue = JudyLIns(&PJLArray, Index, PJE0)

JudyLDel(&PJLArray, Index, &JError)

#define JLD(Rc_int, PJLArray, Index)  \
   Rc_int = JudyLDel(&PJLArray, Index, PJE0)

JudyLGet(PJLArray, Index, &JError)

#define JLG(PValue, PJLArray, Index)  \
   PValue = JudyLGet(PJLArray, Index, PJE0)

JudyLCount(PJLArray, Index1, Index2, &JError)

#define JLC(Rc_word, PJLArray, Index1, Index2)  \
   Rc_word = JudyLCount(PJLArray, Index1, Index2, PJE0)

JudyLByCount(PJLArray, Nth, &Index, &JError)

#define JLBC(PValue, PJLArray, Nth, Index) \
   PValue = JudyLByCount(PJLArray, Nth, &Index, PJE0)

JudyLFreeArray(&PJLArray, &JError)

#define JLFA(Rc_word, PJLArray) \
   Rc_word = JudyLFreeArray(&PJLArray, PJE0)

JudyLMemUsed(PJLArray)

#define JLMU(Rc_word, PJLArray) \
   Rc_word = JudyLMemUsed(PJLArray)

JudyLFirst(PJLArray, &Index, &JError)

#define JLF(PValue, PJLArray, Index) \
   PValue = JudyLFirst(PJLArray, &Index, PJEO)

JudyLNext(PJLArray, &Index, &JError)

#define JLN(PValue, PJLArray, Index) \
   PValue = JudyLNext(PJLArray, &Index, PJEO)

JudyLLast(PJLArray, &Index, &JError)

#define JLL(PValue, PJLArray, Index) \
   PValue = JudyLLast(PJLArray, &Index, PJEO)

JudyLPrev(PJLArray, &Index, &JError)

#define JLP(PValue, PJLArray, Index) \
   PValue = JudyLPrev(PJLArray, &Index, PJEO)

JudyLFirstEmpty(PJLArray, &Index, &JError)

#define JLFE(Rc_int, PJLArray, Index) \
   Rc_int = JudyLFirstEmpty(PJLArray, &Index, PJEO)

JudyLNextEmpty(PJLArray, &Index, &JError)

#define JLNE(Rc_int, PJLArray, Index) \
   Rc_int = JudyLNextEmpty(PJLArray, &Index, PJEO)

JudyLLastEmpty(PJLArray, &Index, &JError)

#define JLLE(Rc_int, PJLArray, Index) \
   Rc_int = JudyLLastEmpty(PJLArray, &Index, PJEO)

JudyLPrevEmpty(PJLArray, &Index, &JError)

#define JLPE(Rc_int, PJLArray, Index) \
   Rc_int = JudyLPrevEmpty(PJLArray, &Index, PJEO)

Определения всех функций Judy, типов Pvoid_t, Pcvoid_t, PPvoid_t, Word_t, JError_t и PJError_t, констант NULL, JU_ERRNO_*, JERR и PJE0 приведены в файле Judy.h (/usr/include/Judy.h). Следует отметить, что вызывающие должны определять массивы Judy1 типа Pvoid_t, который можно передать функциям, принимающим Pcvoid_t (constant Pvoid_t), а также по адресу в функции, принимающие PPvoid_t.

Большинство функций JudyL возвращает PPvoid_t, поскольку значения в массиве могут быть ссылками на другие объекты, или Word_t *, когда нужен указатель на Value, а не указатель на указатель.

JudySL

Макросы JudySL

Макросы JudySL обеспечивают библиотеку C для создания и работы с динамическими массивами, использующими строки с null-символом в конце в качестве Index (ассоциативный массив).

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

void * JudySLArray["Toto, I don't think we're in Kansas any more"];

Массив JudySL создается с указателем NULL

Pvoid_t PJSLArray = (Pvoid_t) NULL;

Как в обычных массивах индексы не дублируются.

При использовании описанных здесь макросов вместо вызова функций JudySL принятая по умолчанию обработка ошибок выводит сообщения на стандартный вывод ошибок и завершает программу с exit(1).

Синтаксис

cc [flags] sourcefiles -lJudy

#include <Judy.h>

#define MAXLINELEN 1000000           // задает максимальный размер строк

Word_t * PValue;                     // элемент массива JudySL
uint8_t  Index[MAXLINELEN];          // строка
int      Rc_int;                     // возвращаемое значение
Word_t   Rc_word;                    // полное слово возвращаемого значения

Pvoid_t PJSLArray = (Pvoid_t) NULL;  // Инициализация массива JudySL

JSLI(PValue,  PJSLArray, Index);     // JudySLIns()
JSLD(Rc_int,  PJSLArray, Index);     // JudySLDel()
JSLG(PValue,  PJSLArray, Index);     // JudySLGet()
JSLFA(Rc_word, PJSLArray);           // JudySLFreeArray()
JSLF(PValue,  PJSLArray, Index);     // JudySLFirst()
JSLN(PValue,  PJSLArray, Index);     // JudySLNext()
JSLL(PValue,  PJSLArray, Index);     // JudySLLast()
JSLP(PValue,  PJSLArray, Index);     // JudySLPrev()

JSLI(PValue, PJSLArray, Index) // JudySLIns()

Вставляет строку Index и Value в массив JudySL PJSLArray. При успешной вставке Index устанавливается Value = 0. Если Index уже присутствует, Value не меняется. Макрос возвращает указатель PValue на значение Value индекса Index. Программы должны использовать этот указатель для изменения Value, например,

*PValue = 1234;

Макросы JSLI() и JSLD реорганизуют массив JudySL, поэтому возвращенные предыдущими вызовами JudySL указатели теряют силу и должны быть определены заново.

JSLD(Rc_int, PJSLArray, Index) // JudySLDel()

Удаляет заданную пару IndexValue (элемент) из массива JudySL. Возвращает Rc_int = 1 при успешном удалении и Rc_int = 0, если Index отсутствует в массиве.

JSLG(PValue, PJSLArray, Index) // JudySLGet()

Возвращает указатель на Value индекса Index или PValue = NULL, если Index отсутствует в массиве.

JSLFA(Rc_word, PJSLArray) // JudySLFreeArray()

По указателю на массив JudySL (PJSLArray) освобождает массив полностью (гораздо быстрей, нежели цикл JSLN(), JSLD()). Возвращает в Rc_word число освобожденных байтов и PJSLArray = NULL.

Средства поиска JudySL

Средства поиска JudySL обеспечивают поиск индексов в массиве (включительный или исключительный) в прямом и обратном направлении.

При успешном поиске Index содержит найденный индекс, а PValue — указатель на значение (Value) для найденного Index. Если поиск не дал результата, возвращается PValue = NULL, а Index не содержит полезной информации. Указатель PValue должен проверяться до использования Index, поскольку при поиске возможны отказы. Для учета всех возможных результатов буфер Index должен иметь размер не меньше максимально длинной строки массива.

JSLF(PValue, PJSLArray, Index) // JudySLFirst()

Включительный поиск первого имеющегося индекса не меньше переданной строки Index. Поиск с пустой (null) строкой индекса будет возвращать первый индекс в массиве. JSLF() обычно применяется для начала упорядоченного сканирования действительных индексов массива JudySL.

uint8_t Index[MAXLINELEN];
strcpy (Index, "");
JSLF(PValue, PJSLArray, Index);

JSLN(PValue, PJSLArray, Index) // JudySLNext()

Исключительный поиск следующего индекса больше переданной строки Index. JSLN() обычно служит для продолжения упорядоченного сканирования действительных индексов в массиве JudySL или поиска «соседа» заданного индекса.

JSLL(PValue, PJSLArray, Index) // JudySLLast()

Включительный поиск последнего имеющегося индекса не больше переданной строки Index. Начало поиска со строки с максимальным значением (например, самой длинной строки с 0xff байтов) возвратит последний индекс в массиве. JSLL() обычно служит для начала упорядоченного сканирования действительных индексов массива JudySL в обратном направлении.

JSLP(PValue, PJSLArray, Index) // JudySLPrev()

Исключительный поиск предыдущего индекса меньше переданной строки Index. JSLP() обычно служит для продолжения упорядоченного сканирования действительных индексов массива JudySL в обратном направлении или поиска «соседа» указанного индекса.

Пример программы сортировки строк

#include <stdio.h>
#include <Judy.h>

#define MAXLINE 1000000                 // максимальный размер строки (длина)

uint8_t   Index[MAXLINE];               // строка для вставки

int     // Синтаксис:  JudySort < file_to_sort
main()
{
    Pvoid_t   PJArray = (PWord_t)NULL;  // Массив Judy.
    PWord_t   PValue;                   // Элемент массива Judy.
    Word_t    Bytes;                    // Размер массива JudySL.

    while (fgets(Index, MAXLINE, stdin) != (char *)NULL)
    {
        JSLI(PValue, PJArray, Index);   // Сохранить строку в массиве.
        if (PValue == PJERR)            // Недостаточно памяти?
        {                               // Обработка нехватки памяти.
            printf("Malloc failed -- get more ram\n");
            exit(1);
        }
        ++(*PValue);                    // Число экземпляров строк.
    }
    Index[0] = '\0';                    // Начать с наименьшей строки.
    JSLF(PValue, PJArray, Index);       // Находит первую строку.
    while (PValue != NULL)
    {
        while ((*PValue)--)             // Выводит дубликаты.
            printf("%s", Index);
        JSLN(PValue, PJArray, Index);   // находит следующую строку.
    }
    JSLFA(Bytes, PJArray);              // Освобождает массив.

    fprintf(stderr, "The JudySL array used %lu bytes of memory\n", Bytes);
    return (0);
}

Функции JudySL

Функции JudySL обеспечивают библиотеку C для создания и работы с динамическими массивами строк, завершающихся null-символом (ассоциативный массив).

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

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

При вызове функций JudySL используется дополнительный параметр по сравнению с соответствующими макросами. Этим параметром является указатель на структуру ошибки или NULL (если детали ошибок не возвращаются).

Ниже функции описаны в терминах их использования макросами (для случая #define JUDYERROR_NOTEST 1). Это является рекомендуемым использованием макросов после завершения отладки программ. Если макрос JUDYERROR_NOTEST не задан, объявляется структура ошибки для хранения информации, возвращаемой из функций JudySL при возникновении ошибок.

Следует обращать внимание на размещение символа & в разных функциях.

Синтаксис

PPvoid_t JudySLIns(PPvoid_t PPJSLArray, const uint8_t * Index, PJError_t PJError);
int      JudySLDel(PPvoid_t PPJSLArray, const uint8_t * Index, PJError_t PJError);
PPvoid_t JudySLGet(Pcvoid_t PJSLArray, const uint8_t * Index, PJError_t PJError);
Word_t   JudySLFreeArray(PPvoid_t PPJSLArray, PJError_t PJError);
PPvoid_t JudySLFirst(Pcvoid_t PJSLArray, uint8_t * Index, PJError_t PJError);
PPvoid_t JudySLNext(Pcvoid_t PJSLArray, uint8_t * Index, PJError_t PJError);
PPvoid_t JudySLLast(Pcvoid_t PJSLArray, uint8_t * Index, PJError_t PJError);
PPvoid_t JudySLPrev(Pcvoid_t PJSLArray, uint8_t * Index, PJError_t PJError);

Определения

JudySLIns(&PJSLArray, Index, &JError)

#define JSLI(PValue, PJSLArray, Index) \
   PValue = JudyLIns(&PJSLArray, Index, PJE0)

JudySLDel(&PJSLArray, Index, &JError)

#define JSLD(Rc_int, PJSLArray, Index) \
   Rc_int = JudySLDel(&PJSLArray, Index, PJE0)

JudySLGet(PJSLArray, Index, &JError)

#define JSLG(PValue, PJSLArray, Index) \
   PValue = JudySLIns(PJSLArray, Index, PJE0)

JudySLFreeArray(&PJSLArray, &JError)

#define JSLFA(Rc_word, PJSLArray) \
   Rc_word = JudySLFreeArray(&PJSLArray, PJE0)

JudySLFirst(PJSLArray, Index, &JError)

#define JSLF(PValue, PJSLArray, Index) \
   PValue = JudySLFirst(PJSLArray, Index, PJE0)

JudySLNext(PJSLArray, Index, &JError)

#define JSLN(PValue, PJSLArray, Index) \
   PValue = JudySLNext(PJSLArray, Index, PJE0)

JudySLLast(PJSLArray, Index, &JError)

#define JSLL(PValue, PJSLArray, Index) \
   PValue = JudySLLast(PJSLArray, Index, PJE0)

JudySLPrev(PJSLArray, Index, &JError)

#define JSLP(PValue, PJSLArray, Index) \
   PValue = JudySLPrev(PJSLArray, Index, PJE0)

Определения всех функций Judy, типов Pvoid_t, Pcvoid_t, PPvoid_t, Word_t, JError_t и PJError_t, констант NULL, JU_ERRNO_*, JERR и PJE0 приведены в файле Judy.h (/usr/include/Judy.h). Следует отметить, что вызывающие должны определять массивы Judy1 типа Pvoid_t, который можно передать функциям, принимающим Pcvoid_t (constant Pvoid_t), а также по адресу в функции, принимающие PPvoid_t.

Большинство функций JudySL возвращает PPvoid_t, поскольку значения в массиве могут быть ссылками на другие объекты, или Word_t *, когда нужен указатель на Value, а не указатель на указатель.

JudyHS

Макросы JudyHS

Библиотека C для создания и работы с динамическими массивами, использующими массив байтов размера Length в качестве Index и word как Value.

Массив JudyHS эквивалентен массиву значений или указателей размера word. Index служит указателем на массив байтов заданного размера Length. Вместо использования строк с null-завершением (как в JudySL) здесь могут применяться строки с любыми битами, включая null. Это дополнение (май 2004 г.) к массивам Judy является комбинацией методов хэширования и Judy. В JudyHS не возникает возможности снижения производительности за счет знания алгоритма хэширования.

Поскольку JudyHS базируется на хэшировании, индексы не сохраняются в каком-либо определенном порядке. Поэтому функции JudyHSFirst(), JudyHSNext(), JudyHSPrev() и JudyHSLast() для поиска соседей не имеют практического значения. Значение Length для каждого массива байтов может меняться от 0 до предела malloc() (около 2 Гбайт).

Отличием JudyHS является скорость и расширяемость при сохранении эффективности использования памяти. Скорость сопоставима с лучшими методами хэширования, а эффективность использования памяти близка к связным спискам для таких же Index и Value. Массивы JudyHS рассчитаны на работу с числом индексов от 0 до миллиардов.

Массив JudyHS создается с указателем NULL.

Pvoid_t PJHSArray = (Pvoid_t) NULL;

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

Синтаксис

cc [flags] sourcefiles -lJudy

#include <Judy.h>

Word_t  * PValue;                           // элемент массива JudyHS
int       Rc_int;                           // код возврата
Word_t    Rc_word;                          // полное значение возвращаемого слова
Pvoid_t   PJHSArray = (Pvoid_t) NULL;       // инициализация массива JudyHS
uint8_t * Index;                            // указатель на массив байтов
Word_t    Length;                           // число байтов в Index

JHSI( PValue,  PJHSArray, Index, Length);   // JudyHSIns()
JHSD( Rc_int,  PJHSArray, Index, Length);   // JudyHSDel()
JHSG( PValue,  PJHSArray, Index, Length);   // JudyHSGet()
JHSFA(Rc_word, PJHSArray);                  // JudyHSFreeArray()

Определения

JHSI(PValue, PJHSArray, Index, Length) // JudyHSIns()

По указателю на массив JudyHS (PJHSArray) вставляет строку Index размера Length и Value в массив JudyHS PJHSArray. При успешной вставке Index устанавливается Value = 0. Если Index уже есть в массиве, Value не меняется. Возвращаемое значение PValue указывает на Value. Программам следует использовать этот указатель для изменения Value, например,

Value = *PValue;
*PValue = 1234;

Отметим, что JHSI() и JHSD могут реорганизовать массив JudyHS, поэтому возвращенные из предыдущих вызовов JudyHS указатели должны быть обновлены (с помощью JHSG()).

JHSD(Rc_int, PJHSArray, Index, Length) // JudyHSDel()

По указателю на массив JudyHS (PJHSArray) удаляет указанный Index вместе с Value из массива JudyHS. Возвращает Rc_int = 1 при успешном удалении и Rc_int = 0, если Index отсутствует.

JHSG(PValue, PJHSArray, Index, Length) // JudyHSGet()

По указателю на массив JudyHS (PJHSArray) находит значение Value, связанное с Index. Возвращает указатель PValue на значение Value для Index или PValue = NULL при отсутствии Index.

JHSFA(Rc_word, PJHSArray) // JudyHSFreeArray()

По указателю на массив JudyHS (PJHSArray) освобождает массив целиком. Возвращает в Rc_word число освобожденных байтов и PJHSArray = NULL.

Пример

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

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <Judy.h>

// Команда компиляции cc -O PrintDupLines.c -lJudy -o PrintDupLines

#define MAXLINE 1000000                 /* Максимальный размер строки */
uint8_t   Index[MAXLINE];               // Проверяемая строка

int     // Команда PrintDupLines < file
main()	{
    Pvoid_t   PJArray = (PWord_t)NULL;  // Массив Judy.
    PWord_t   PValue;                   // Указатель на элемент массива Judy.
    Word_t    Bytes;                    // Размер массива JudyHS.
    Word_t    LineNumb = 0;             // Номер текущей строки.
    Word_t    Dups = 0;                 // Число строк-дубликатов.

    while (fgets(Index, MAXLINE, stdin) != (char *)NULL)	{
        LineNumb++;                     // Номер строки.

        // Сохранение строки в массиве
        JHSI(PValue, PJArray, Index, strlen(Index)); 
        if (PValue == PJERR)	{      // См. Ошибки.
            fprintf(stderr, "Out of memory -- exit\n");
            exit(1);
        }
        if (*PValue == 0)	{             // Проверка дублирования
            Dups++;
            printf("Duplicate lines %lu:%lu:%s", *PValue, LineNumb, Index);
        } else {
            *PValue = LineNumb;         // Сохранение номера строки
        }
    }
    printf("%lu Duplicates, free JudyHS array of %lu Lines\n", 
                    Dups, LineNumb - Dups);
    JHSFA(Bytes, PJArray);              // Освобождение массива JudyHS
    printf("JudyHSFreeArray() free'ed %lu bytes of memory\n", Bytes);
    return (0);
}

Функции JudyHS

Функции JudySL обеспечивают библиотеку C для создания и работы с динамическими массивами из массивов байтов размера Length с использованием индексов типа word.

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

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

При вызове функций JudyHS используется дополнительный параметр по сравнению с соответствующими макросами. Этим параметром является указатель на структуру ошибки или NULL (если детали ошибок не возвращаются).

Ниже функции описаны в терминах их использования макросами (для случая #define JUDYERROR_NOTEST 1). Это является рекомендуемым использованием макросов после завершения отладки программ. Если макрос JUDYERROR_NOTEST не задан, объявляется структура ошибки для хранения информации, возвращаемой из функций JudyHS при возникновении ошибок.

Следует обращать внимание на размещение символа & в разных функциях.

Синтаксис

PPvoid_t JudyHSIns(PPvoid_t PPJHS, void *Index, Word_t Length, PJError_t PJError);
int      JudyHSDel(PPvoid_t PPJHS, void *Index, Word_t Length, PJError_t PJError);
PPvoid_t JudyHSGet(Pcvoid_t  PJHS, void *Index, Word_t Length, PJError_t PJError);
Word_t   JudyHSFreeArray(PPvoid_t PPJHS, PJError_t PJError);

Определения

JudyHSIns(&PJHS, Index, Length, &JError)

#define JHSI(PValue, PJHS, Index) \
   PValue = JudyLIns(&PJHS, Index, PJE0)

JudyHSDel(&PJHS, Index, Length, &JError)

#define JHSD(Rc_int, PJHS, Index, Length) \
   Rc_int = JudyHSDel(&PJHS, Index, Length, PJE0)

JudyHSGet(PJHS, Index, Length)

#define JHSG(PValue, PJHS, Index, Length) \
   PValue = JudyHSIns(PJHS, Index, Length)

JudyHSFreeArray(&PJHS, &JError)

#define JHSFA(Rc_word, PJHS) \
   Rc_word = JudyHSFreeArray(&PJHS, PJE0)

Определения всех функций Judy, типов Pvoid_t, Pcvoid_t, PPvoid_t, Word_t, JError_t и PJError_t, констант NULL, JU_ERRNO_*, JERR и PJE0 приведены в файле Judy.h (/usr/include/Judy.h). Следует отметить, что вызывающие должны определять массивы Judy1 типа Pvoid_t, который можно передать функциям, принимающим Pcvoid_t (constant Pvoid_t), а также по адресу в функции, принимающие PPvoid_t.

Большинство функций JudyHS возвращает PPvoid_t, поскольку значения в массиве могут быть ссылками на другие объекты, или Word_t *, когда нужен указатель на Value, а не указатель на указатель.


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

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

nmalykh@protokols.ru

1В процессе тестирования Judy было найдено очень мало malloc()/ОС, где не возникало ошибок при отказах malloc(). Иногда приходилось ждать очень долго, поскольку в большинстве систем запускалась «бесконечная» подкачка.

2В текущей версии Judy.h значение этого флага 0x4 было заменено на 0x1, чтобы можно было использовать функцию malloc(), не выравнивающую выделенную память по 8-битовой границе (например, старая версия valgrind).

 

Рубрика: Алгоритмы | Комментарии к записи Библиотека Judy для работы с динамическими массивами отключены

SiFive-OE-poPingUI-P4

PDF

Для экспериментов с применением языка P4 (p4.org) в сетевых устройствах была предпринята попытка сборки прототипа компилятора P4C на платформе HiFive Unleashed U540 компании SiFive. В качестве среды разработки использовалась система OpenEmbedded (www.yoctoproject.org) и базовый репозиторий SiFive (github.com/sifive/meta-sifive/tree/master). Для работы также требуется пакет repo, установка которого описана по ссылке.

Для сборки образов потребуется достаточно большое пространство на диске (после сборки образа на диске было занято 94 Гбайт), а сам диск должен быть быстрым (SSD или SAS).

  1. Создаем пустой каталог и переходим в него

    mkdir sifive-master
    cd sifive-master

    Далее в тексте этот каталог будет именоваться корневым и все ссылки на каталоги и файлы будут отсчитываться от него.

  2. Создаем и заполняем репозиторий

    $ repo init -u git://github.com/sifive/meta-sifive -b master -m tools/manifests/sifive.xml
    $ repo sync
  3. Создаем уровень meta-poPingUI

    $ bitbake-layers create-layer meta-poPingUI
    NOTE: Starting bitbake server... 
    Add your new layer with 'bitbake-layers add-layer meta-poPingUI'
    $ bitbake-layers add-layer meta-poPingUI
    NOTE: Starting bitbake server... 
    Unable to find bblayers.conf
  4. Копируем файл установки окружения из каталога meta-sifive

    $ cp meta-sifive/setup.sh meta-poPingUI/setup.sh
  5. Редактируем созданную копию, добавляя строку

    $ bitbake-layers add-layer ../meta-poPingUI
  6. Из строки DISTRO_FEATURES_append = » largefile opengl ptest multiarch pam systemd vulkan » удаляем opengl и vulkan и получаем в результате

    DISTRO_FEATURES_append = " largefile ptest multiarch pam systemd "
  7. В начале файла меняем имя создаваемого образа, как показано ниже

    BITBAKEIMAGE="coreip-cli"
  8. В конце файла помещаем символ комментария в показанные ниже строки

    # Add r600 drivers for AMD GPU 
    #PACKAGECONFIG_append_pn-mesa = " r600" 
    
    # Add support for modern AMD GPU (e.g. RX550 / POLARIS) 
    #PACKAGECONFIG_append_pn-mesa = " radeonsi" 
    #PACKAGECONFIG_append_pn-mesa = " gallium-llvm"
  9. Копируем каталог meta-sifive/conf в meta-poPingUI/conf

  10. Редактируем файл meta-poPingUI/conf/layer.conf, заменяя sifive на poPingUI, а также устанавливаем для уровня высокий приоритет, чтобы при совпадении имен задания выбирались из нашего уровня

    BBFILE_PRIORITY_meta-poPingUI = "9"
  11. Копируем каталог meta-sifive/recipes-sifive/images в meta-poPingUI/recipes-poPingUI/images

    Поскольку планируется работа лишь с платой HiFive без модулей расширения удаляем файлы с поддержкой графического интерфейса (demo-coreip-xfce4.bb и demo-coreip-xfce4-debug.bb), а файлы demo-coreip-cli.bb и demo-coreip-cli-debug.bb переименовываем в coreip-cli.bb и coreip-cli-debug.bb. Это позволит избежать путаницы и при необходимости воспользоваться образами уровня meta-sifive.

  12. Переходим в корневой каталог репозитория и вводим команду

    $ . ./meta-poPingUI/setup.sh
    Init OE 
    You had no conf/local.conf file. This configuration file has therefore been 
    created for you with some default values. You may wish to edit it to, for 
    example, select a different MACHINE (target hardware). See conf/local.conf 
    for more information as common configuration options are commented. 
    
    You had no conf/bblayers.conf file. This configuration file has therefore been 
    created for you with some default values. To add additional metadata layers 
    into your configuration please add entries to conf/bblayers.conf. 
    
    The Yocto Project has extensive documentation about OE including a reference 
    manual which can be found at: 
       http://yoctoproject.org/documentation 
    
    For more information about OpenEmbedded see their website: 
       http://www.openembedded.org/ 
    
    
    ### Shell environment set up for builds. ### 
    
    You can now run 'bitbake <target>' 
    
    Common targets are: 
       core-image-minimal 
       core-image-sato 
       meta-toolchain 
       meta-ide-support 
    
    You can also run generated qemu images with a command like 'runqemu qemux86'. 
    
    Other commonly useful commands are: 
    - 'devtool' and 'recipetool' handle common recipe tasks 
    - 'bitbake-layers' handles common layer tasks 
    - 'oe-pkgdata-util' handles common target package tasks 
    Adding layers 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    NOTE: Starting bitbake server... 
    Creating auto.conf 
    --------------------------------------------------- 
    MACHINE=freedom-u540 bitbake coreip-cli 
    --------------------------------------------------- 
    
    Buildable machine info 
    --------------------------------------------------- 
    * freedom-u540: The SiFive HiFive Unleashed board 
    * qemuriscv64: The 64-bit RISC-V machine 
    --------------------------------------------------- 
    
  13. Запускаем сборку образа командой

    $ MACHINE=freedom-u540 bitbake coreip-cli
    Parsing recipes: 100% |#############################################################################################################################################################################| Time: 0:00:18 
    Parsing of 2346 .bb files complete (0 cached, 2346 parsed). 3445 targets, 174 skipped, 0 masked, 0 errors. 
    NOTE: Resolving any missing task queue dependencies 
    
    Build Configuration: 
    BB_VERSION           = "1.46.0" 
    BUILD_SYS            = "x86_64-linux" 
    NATIVELSBSTRING      = "mageia-7" 
    TARGET_SYS           = "riscv64-oe-linux" 
    MACHINE              = "freedom-u540" 
    DISTRO               = "nodistro" 
    DISTRO_VERSION       = "nodistro.0" 
    TUNE_FEATURES        = "riscv64" 
    meta                 = "HEAD:57ccf1e3bb320bd28a2d106c98f4706434c3075a" 
    meta-oe               
    meta-python           
    meta-multimedia       
    meta-networking       
    meta-gnome            
    meta-xfce            = "HEAD:920161113533074d27dc93c521815380fdf20275" 
    meta-riscv           = "HEAD:8bd3402c76f897189842f65e521f1388777660ca" 
    meta-sifive          = "HEAD:4c97a625e70558fbca9dc210616b13115e22dbee" 
    meta-poPingUI        = "<unknown>:<unknown>"
    
    
  14. Процесс загрузки исходных кодов1 и сборки компонент достаточно долог и можно заняться другими делами. В моей системе Mageia 7.1 процесс завершается ошибкой

    virtual:native:/OE/sifive-master/openembedded-core/meta/recipes-devtools/perl/libxml-parser-perl_2.46.bb:do_configure

    Для решения этой проблемы создаем в каталоге уровня файл meta-poPingUI/recipes-devtools/libxml-parser-perl/libxml-parser-perl_2.46.bbappend, показанный ниже

    do_configure_prepend_class-native () { 
       cd ${S} 
       ${HOSTTOOLS_DIR}/perl Makefile.PL 
       cd - 
    }
  15. Проблема с networkmanager решается командой LC_ALL=C ../recipe-sysroot-native/usr/bin/intltool-merge -x -u -c ./po/.intltool-merge-cache ../NetworkManager-1.22.10/po data/org.freedesktop.NetworkManager.policy.in data/org.freedesktop.NetworkManager.policy из каталога build/tmp-glibc/work/riscv64-oe-linux/networkmanager/1.22.10-r0/build и создания символьной ссылки /usr/bin/nativeperl на локальный перл хоста2

  16. На этом первичные правки завершаются и образ собирается нормально. Можно начинать внесение своих правок.

  17. Сначала добавим Judy, поскольку от этого пакета зависит p4c и связанные пакеты.

    $ devtool add https://github.com/multiSnow/judy.git
    $ bitbake judy

    Процесс завершается ошибкой /lib/ld-linux-riscv64-lp64d.so.1: No such file or directory. Это связано с попыткой запуска двоичного файла генерации таблиц, собранного для RISCV, из среды сборки x86. Найти способ обхода этой проблемы мне не удалось, поэтому были просто взяты файлы, созданные на платформе HiFive Unleashed, и помещены в каталоги исходного кода judy. При этом запуск программы генерации (JudyLTablesGen и Judy1TablesGen) из Makefile был удален путем простого редактирования.

    Копируем файл JudyLTables.c в каталог build/tmp-glibc/work/riscv64-oe-linux/judy/1.0.5+git999-r0/judy-1.0.5+git999/src/JudyL и удаляем конец строки 7953 ./JudyLTablesGen. Затем повторяем команду bitbake judy и выполняем аналогичные процедуры в каталоге Judy1 с файлом Judy1Tables.c. Следующий запуск команды bitbake judy обеспечивает сборку пакета без ошибок.

  18. Добавляем созданное задание на уровень meta-poPingUI командой

    $ devtool finish -f judy meta-poPingUI/recipes-devtools

    из корневого каталога системы сборки. В результате готовое задание помещается на нужный уровень в каталог recipes-devtools. После этого следует повторить сборку задания, поскольку при переносе внесенные изменения теряются (см. 14). Операции выполняются с файлами в каталоге build/tmp-glibc/work/riscv64-oe-linux/judy/1.0.5+gitAUTOINC+a5971f3ee4-r0/build/src.

  19. Следующим добавляем пакет PI

    $ devtool add https://github.com/p4lang/PI.git

    В файле задания pi_git.bb меняем строку DEPENDS, как показано ниже

    DEPENDS = "readline judy nanomsg protobuf protobuf-c"
    

    И добавляем опции настройки

    EXTRA_OECONF = " --with-fe-cpp --with-proto=Protobuf --with-internal-rpc --with-cli "

    Между пакетами PI и bmv2 имеются циклические зависимости, поэтому PI придется собрать дважды без поддержки bmv2 (без опции —with-bmv2). Включаем задание на уровень meta-poPingUI командами из корневого каталога

    $ mkdir meta-poPingUI/recipes-p4
    $ devtool finish pi meta-poPingUI/recipes-p4
    $ bitbake pi
    
  20. Затем добавляем пакет bmv2 (behavioral model version 2), содержащий прототип коммутатора P4.

    $ devtool add https://github.com/p4lang/behavioral-model.git

    Система сборки создает для него задание с именем bm. Редактируем в файле задания bm_git.bb приведенные ниже строки

    DEPENDS = "gmp judy nanomsg libpcap pi boost"
    EXTRA_OECONF = " --with-nanomsg --with-pi --enable-modules --disable-dependency-tracking --without-thrift "
    

    Затем переносим задание на уровень meta-poPingUI командой из корневого каталога

    $ devtool finish bm meta-poPingUI/recipes-p4
  21. Начинаем сборку компилятора P4C

    $ devtool add https://github.com/p4lang/p4c.git

    Файл задания корректируем как показано ниже

    DEPENDS = "flex-native bison-native boost protobuf protobuf-native protobuf-c protobuf-c-native doxygen bm bdwgc gmp judy" 
    EXTRA_OECMAKE = "-DENABLE_GC=OFF -DENABLE_EBPF=OFF -DENABLE_PROTOBUF_STATIC=OFF "

    Сборка завершается ошибкой, связанной с запуском исполняемого файла RISCV (tools/irgenerator) в среде сборки x86. Для решения проблемы помещаем символ комментария в начало строки с вызовом команды irgenerator (строка 3194) в файле build.ninja каталога build/workspace/sources/p4c/oe-workdir/p4c-1.0+git999. Затем нужно скопировать в каталог build/workspace/sources/p4c/oe-workdir/p4c-1.0+git999/ir заранее подготовленные на целевой платформе файлы, перечисленные ниже

    gen-tree-macro.h 
    gen-tree-macro.h.fixup 
    gen-tree-macro.h.tmp 
    ir-generated.cpp 
    ir-generated.cpp.fixup 
    ir-generated.cpp.tmp 
    ir-generated.h 
    ir-generated.h.fixup 
    ir-generated.h.tmp 
    libir.a

    Копируем задание на уровень meta-poPingUI командой из корневого каталога

    $ devtool finish bm meta-poPingUI/recipes-p4

    После чего повторяем сборку и после получения ошибки внесим описанные выше правки в каталоге build/tmp-glibc/work/riscv64-oe-linux/p4c/1.0+gitAUTOINC+f79af56ea9-r0/build и еще раз собираем пакет командой

    $ bitbake p4c
  22. Сборка завершается без ошибок и можно внести задание в образ, редактируя файл meta-poPingUI/recipes-poPingUI/images/coreip-cli.bb. Для этого добавляем строку в конце переменной IMAGE_INSTALL, как показано ниже

       p4c \ 
       ${CORE_IMAGE_EXTRA_INSTALL} \ 
       " 
    
    IMAGE_INSTALL_append_freedom-u540 = "\ 
       unleashed-udev-rules \ 
       "
  23. Далее создаем образ командой

    $ MACHINE=freedom-u540 bitbake coreip-cli

    и по завершении его создания переносим на SD-карту командой

    $ zcat build/tmp-glibc/deploy/images/freedom-u540/coreip-cli-freedom-u540.wic.gz | sudo dd of=/dev/sdX bs=512K iflag=fullblock oflag=direct conv=fsync status=progress

    где sdX — имя устройства (SD-карта).

  24. По завершении записи можно отмонтировать SD-карту и перенести ее на плату HiFive Unleased. Для входа в систему служит имя пользователя root с паролем sifive.

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

Работа выполнена в рамках проекта «Орион».


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

nmalykh@protokols.ru


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

2Попытка выполнить команду из файла дополнения дает ошибку «You must have XML::Parser installed to run ../recipe-sysroot-native/usr/bin/intltool-merge». Добавление зависимостей проблему не решает.

3Номер строки может отличаться, поэтому лучше воспользоваться контекстным поиском в файле.

4Можно найти строку по контексту Generating IR class files. Искомая строка будет предыдущей.

Рубрика: Linux, RISC-V, Сетевое программирование | Комментарии к записи SiFive-OE-poPingUI-P4 отключены

Corundum FPGA

Сетевой адаптер Corundum на основе ПЛИС

PDF

По материалам [1]

Введение

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

В соответствии с потребностями в открытой платформе для разработки протоколов и архитектуры сетей на реальных скоростях передачи данных предложена платформа с открытым исходным кодом для создания прототипов NIC на основе FPGA. Эта платформа, названная Corundum [1], способна работать со скоростью не менее 94 Гбит/с, имеет открытый исходный код и вместе с драйвером может применяться в полном сетевом стеке. Решение является переносимым и компактным, поддерживая множество разных устройств и сохраняя ресурсы для дополнительной настройки даже в небольших устройствах. Модульное и расширяемое устройство Corundum позволяет оптимизировать программно-аппаратные решения для создания и тестирования сетевых приложений в реальных условиях.

Предпосылки

Мотивы разработки Corundum становятся понятными, если рассмотреть распределение функций сетевого интерфейса между программной и аппаратной частью имеющихся NIC. Аппаратные функции NIC делятся на две основные категории. Первая категория включает простые функции разгрузки, принимающие на себя часть работы процессора (CPU) по обработке каждого пакета, такие как расчёт контрольной суммы или свёртки (hash) и сегментация, что позволяет сетевому стеку выполнять групповую (batch) обработку пакетов. Ко второй категории относятся функции, которые нужно выполнять на аппаратном уровне NIC для обеспечения высокой производительности и беспристрастности. К таким функциям относится управление потоками, распределение нагрузки и временные метки. Традиционно аппаратные возможности NIC реализуются с помощью фирменных микросхем, ориентированных на конкретные задачи (application-specific integrated circuit или ASIC). Это позволяет достичь высокой производительности при малой стоимости. Однако расширяемость таких ASIC сильно ограничена, а цикл разработки для расширения функциональности достаточно дорог и продолжителен [2]. Чтобы преодолеть эти ограничения, было разработано множество интеллектуальный (smart) и программных NIC. Интеллектуальные сетевые адаптеры обеспечивают программируемость обычно за счёт предоставления множества программируемых вычислительных ядер и аппаратных примитивов. Эти ресурсы могут применяться для выгрузки с хоста различных прикладных, сетевых и связанных с виртуализацией операций. Однако интеллектуальные NIC не всегда подходят для высокоскоростных линий и аппаратные функции могут быть ограничены [2].

Программные NIC обеспечивают большую гибкость за счёт программной реализации сетевых функций, минуя большую часть выгрузки в оборудование. Это обеспечивает быструю разработку и тестирование функций, но требует больше ресурсов CPU хоста и не всегда позволяет работать со скоростью линии. Кроме того, присущий программам на основе прерываний элемент случайности не позволяет создавать сетевые приложения, требующие точного управления передачей [3]. Тем не менее, во многих исследовательских проектах [4]-[7] были программно реализованы новые функции NIC путём изменения сетевого стека или применения схем обхода ядра, таких как DPDK1 [8].

Сетевые адаптеры на основе ПЛИС (FPGA) сочетают свойства NIC на основе ASIC и программных NIC — они могут работать со скоростью линии, обеспечивают малые задержки и точную синхронизацию, сохраняя достаточно короткий цикл разработки новых функций. Разработано достаточно фирменных NIC на основе ПЛИС. Например, компания Alibaba создала полностью настраиваемый сетевой адаптер ПЛИС на основе RDMA, который применяется для аппаратной реализации протокола прецизионного контроля перегрузок (High precision congestion control protocol или HPCC) [9]. Имеется и коммерческая продукция, например от Exablaze2 [9] и Magmio [10]. К сожалению, коммерческие сетевые адаптеры на основе ПЛИС (как и ASIC NIC) часто используют закрытые фирменные функции, которые невозможно изменить. Это ограничивает гибкость и применимость таких NIC при разработке новых сетевых приложений. Доступные коммерчески высокопроизводительные компоненты DMA, такие как ядро Xilinx XDMA, ядра QDMA, ускорительное ядро Atomic Rules Arkville DPDK [12] не предоставляют полностью настраиваемого оборудования для контроля потока передаваемых данных. Ядро Xilinx XDMA предназначено для выгрузки приложений, поэтому обеспечивает сильно ограниченную функциональность очередей и не предоставляет простого метода управления планированием передачи. Ядро Xilinx QDMA и ускорительное ядро Atomic Rules Arkville DPDK ориентированы на сетевые приложения, поддерживая небольшое число очередей и предоставляя драйверы DPDK. Однако поддерживаемое число очередей (2K в XDMA и до 128 в Arkville) невелико и нет простого метода точного управления передачей пакетов.

Имеется проект с открытым кодом NetFPGA [13], однако он предоставляет лишь инструменты для базовой обработки пакетов на основе FPGA и не предназначен для разработки NIC. Кроме того, эталонная модель NetFPGA NIC использует фирменное ядро Xilinx XDMA, не предназначенное для сетевых приложений. Замена ядра Xilinx XDMA в эталонном NIC для NetFPGA на Corundum расширяет возможности и повышает гибкость создания прототипов.

Решения по обработке пакетов на основе FPGA включают Catapult [14] с выгрузкой приложений и FlowBlaze [15] с реализацией механизмов «сопоставление-действие» (match-action) на основе ПЛИС. Однако эти платформы оставляют реализацию стандартных функций NIC за ASIC и работают в режиме «насадки на провод» (bump-in-the-wire), не обеспечивая явного управления планировщиком и очередями в NIC.

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

Реализация Corundum

Corundum имеет ряд уникальных архитектурных свойств. Во-первых, состояния аппаратных очередей сохраняются в оперативной памяти (RAM) FPGA, что позволяет поддерживать тысячи очередей с индивидуальным управлением. Эти очереди связаны с интерфейсами, а каждый интерфейс может иметь несколько портов, в которых могут применяться свои независимые планировщики передачи. Такой подход обеспечивает очень тонкое управления передачей пакетов. Модуль планировщика можно изменить или заменить полностью для реализации иных схем планирования, включая экспериментальные. В сочетании с синхронизацией часов по протоколу PTP это обеспечивает возможность планирования по времени, включая высокоточный доступ TDMA3.

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

Решение поддерживает компоненты DMA PCIe для интерфейса ядра Xilinx Ultrascale PCIe hard IP. Поддержка интерфейса PCIe TLP, обычно применяемая в FPGA, не реализована и оставлена на будущее. Это позволит расширить набор подходящих ПЛИС.

Corundum занимает достаточно малую площадь, оставляя достаточно места для дополнительной логики даже в сравнительно мелких FPGA. Например, Corundum для ExaNIC X10 [10] (2 порта 10G с интерфейсом PCIe gen 3 x8 и 512-битовым внутренним путём данных) занимает менее четверти ресурсов, доступных на одном из самых мелких Kintex Ultrascale FPGA (KU035).

Высокоуровневый обзор

 Рисунок 1. Блок-схема Corundum NIC.
PCIe HIP — ядро PCIe hard IP, AXIL M — AXI lite master, DMA IF — интерфейс DMA, PTP HC — аппаратные часы PTP, TXQ — менеджер очередей передачи, TXCQ — менеджер очередей завершения передачи, PTP, RXQ — менеджер очередей приёма, RXCQ — менеджер очередей завершения приёма, EQ — менеджер событий в очередях, MAC + PHY — контроллер доступа к среде Ethernet (MAC) и уровень физического интерфейса (PHY).

Блок-схема Corundum NIC показана на рисунке 1. На верхнем уровне NIC состоит из 3 основных вложенных модулей. Модуль верхнего уровня содержит компоненты поддержки и интерфейсов, включая ядро PCI express hard IP и интерфейс прямого доступа к памяти (DMA), аппаратные часы PTP, компоненты интерфейса Ethernet с MAC, PHY и сериализаторами4. Модуль верхнего уровня включает также 1 или несколько экземпляров интерфейсных модулей, каждый из которых соответствует сетевому интерфейсу операционной системы (например, eth0). Каждый интерфейсный модуль включает логику управления очередью, а также логику дескрипторов и завершения обработки. Логика обработки очереди поддерживает состояния для всех очередей NIC — передача, завершение передачи, приём, завершение приёма, события в очереди. В каждом интерфейсном модуле содержится 1 или несколько экземпляров модулей порта. Каждый модуль порта предоставляет потоковый интерфейс AXI в MAC и содержит планировщик передачи, механизмы передачи и приёма, пути данных для передачи и приёма, а также память (RAM) для временного сохранения входящих и исходящих пакетов при операциях DMA.

Для каждого порта планировщик передачи в модуле порта выбирает очереди, назначенные для отправки. Планировщик передачи генерирует команды для механизма передачи, который координирует операции на пути передачи. Модуль планировщика является гибким функциональным блоком, который можно изменить или заменить произвольным планировщиком (например, по событиям). По умолчанию планировщик просто использует круговой обход (round robin). Для всех портов, связанных с одним интерфейсным модулем, используется общий набор очередей передачи, который представляется как один унифицированный интерфейс с операционной системой. Это позволяет перемещать потоки из порта в порт или распределять нагрузку между несколькими портами просто сменой установок планировщика передачи без влияния на остальной сетевой стек. Динамическое сопоставление очередей с портами, задаваемое планировщиком, является уникальным свойством Corundum, позволяющим исследовать новые протоколы и архитектуру сети, включая параллельные сети, такие как P-FatTree [16], и сети с оптической коммутацией, такие как RotorNet [17] и Opera [18].

В направлении приёма входящие пакеты проходят через модуль хэширования потока (Hash) для определения целевой приёмной очереди и генерации команд для машины приёма (RX engine), координирующей операции на приёмном пути данных. Поскольку все порты одного интерфейсного модуля используют общий набор приёмных очередей, входящие потоки от разных портов сливаются в общий набор очередей. В NIC можно добавить настраиваемые модули для предварительной обработки и фильтрации входящих пакетов до их передачи на шину PCIe.

Компоненты NIC соединены между собой через несколько разных интерфейсов, включая AXI lite, AXI stream и сегментируемый интерфейс с памятью для операций DMA, который будет описан ниже. AXI lite служит для пути управления от драйвера к NIC. Он применяется для инициализации и настройки компонентов NIC, а также для управления указателями очередей при операциях приёма и передачи. Интерфейсы AXI stream служат для передачи внутри NIC пакетизированных данных, включая пакеты уровня передачи PCIe (PCIe transmission layer packet или TLP) и кадры Ethernet. Сегментированный интерфейс с памятью служит для соединения интерфейса PCIe DMA с трактом данных NIC, а также логикой дескрипторов и завершения обработки.

Большая часть логики NIC работает в пользовательском домене синхронизации PCIe с номинальной частотой 250 МГц для всех современных вариантов исполнения. Для взаимодействия с MAC применяются асинхронные буферы FIFO, работающие в соответствующих доменах синхронизации приёма и передачи с частотой 156,25 МГц для 10G, 390,625 МГц для 25G и 322,266 МГц для 100G.

Управление конвейерными очередями

Обмен пакетами данных между Corundum NIC и драйвером осуществляется через очереди дескрипторов и завершения. Очереди дескрипторов формируют коммуникационный канал от хоста к NIC для передачи сведений о местах хранения отдельных пакетов в памяти системы. Очереди завершения формируют коммуникационный канал от NIC к хосту для передачи сведений о завершённых операциях и связанных с ними метаданных. Очереди дескрипторов и завершения реализованы в форме кольцевых буферов в доступной через DMA памяти системы, а оборудование NIC поддерживает требуемые сведения о состоянии очередей. Данные состояния включают адрес DMA для кольцевого буфера, указатели на производителя и потребителя, с также ссылку на соответствующую очередь завершения. Состояние дескриптора для каждой очереди помещается в 128 битов.

Логика управления очередями для Corundum NIC должна эффективно сохранять и поддерживать состояния тысяч очередей. Ото требует хранить состояние очереди в блочном ОЗУ (block RAM или BRAM5) или ultra RAM (URAM6) на FPGA. Поскольку для состояния требуется 128 битов ОЗУ, а блоки URAM имеют размер 72×4096, для хранения 4096 очередей нужно 2 экземпляра URAM. Использование экземпляров URAM позволяет расширять логику управления очередями до 32768 очередей на интерфейс.

Для поддержки высокой пропускной способности от NIC требуется параллельная обработка дескрипторов. Поэтому логика управления очередями должна отслеживать множество выполняющихся операций, передавая обновлённые указатели очередей драйверу при завершении операций. Требуемое для отслеживания выполняемых операций состояние значительно меньше состояния дескриптора и поэтому может сохраняться в триггерах (flip-flop) и распределенном ОЗУ.

В NIC используется 2 модуля диспетчера очередей — queue_manager для управления очередями дескрипторов host-to-NIC и cpl_queue_manager для управления очередями завершения NIC-to-host. Модули незначительно различаются в части обработки указателей и генерации событий. Из-за сходства модулей здесь описан лишь модуль queue_manager.

Массив BRAM или URAM, применяемый для хранения данных о состоянии очередей, требует задержки в несколько тактов для каждой операции чтения, поэтому в queue_manager используется конвейерная архитектура для облегчения одновременного выполнения нескольких операций. Конвейер поддерживает 4 типа операций — чтение и запись регистра, запрос на постановку или извлечение из очереди. Операции доступа к регистрам через интерфейс AXI позволяют драйверу инициализировать состояние очереди и предоставлять указатели на выделенную память хоста, а также обращаться к указателям производителя и потребителя при обычных операциях.

Планировщик передачи

По умолчанию в Corundum NIC применяется простой планировщик передачи с круговым обходом, реализованный в модуле tx_scheduler_rr. Планировщик передаёт команды механизму передачи для запуска операций передачи из очередей NIC. Планировщик содержит базовые состояния для всех очередей, буфер FIFO для хранения текущих активных очередей и выполнения кругового обхода, а также таблицу операций для отслеживания выполняющихся передач.

Подобно логике управления очередями планировщик хранить сведения о состоянии очередей в BRAM или URAM на FPGA, что позволяет расширять планировщик для поддержки большого числа очередей. Планировщик передачи тоже использует конвейерную обработку для сокрытия задержки доступа к памяти. Модуль планировщика передачи включает 4 основных интерфейса — интерфейс регистра AXI lite и 3 потоковых интерфейса. Интерфейс AXI lite позволяет драйверу менять параметры планировщика, а также включать и отключать очереди. Первый потоковый интерфейс обеспечивает получение событий (doorbell) от логики управления очередями, когда драйвер помещает пакеты для передачи в очередь. Второй потоковый интерфейс передаёт команды отправки, генерируемые планировщиком для машины передачи. Каждая команда включает индекс очереди, из которой нужно передавать, а также тег для отслеживания выполняющихся операций. Третий потоковый интерфейс возвращает планировщику сведения о статусе операции передачи, сообщая размер переданного пакета или причину отказа (пустая или отключённая очередь).

Планировщик передачи можно расширить или заменить для реализации любых алгоритмов планирования. Это позволяет использовать Corundum как платформу для оценки экспериментальных алгоритмов планирования, таких как SENIC [4], Carousel [5], PIEO [19], Loom [7]. Можно также предоставить дополнительные входя в модуль планировщика передачи, включая обратную связи от пути приёма, которые позволяют реализовать новые протоколы и методы контроля перегрузок, такие как NDP [6] и HPCC [9]. Соединение планировщика с аппаратными часами PTP позволяет поддерживать TDMA для реализации RotorNet [17], Opera [18] и других моделей коммутации каналов.

Порты и интерфейсы

(a) Традиционный сетевой адаптер с программным назначением портов.

(b) Corundum NIC с аппаратным назначением портов.

Рисунок 2. Сравнение архитектуры портов и интерфейсов.

Уникальной архитектурной особенностью Corundum является разделение портов и сетевых интерфейсов, позволяющее связать с одним интерфейсом несколько портов. Большинство современных NIC поддерживают 1 порт на интерфейс, как показано на рисунке 2(a). Когда сетевой стек помещает пакет в очередь передачи на сетевом интерфейсе, пакет попадает в сеть через связанный с этим интерфейсом порт. Однако в Corundum с интерфейсом может быть связано несколько портов, поэтому выбор порта для передачи пакета в сеть может определяться оборудованием при извлечении из очереди, как показано на рисунке 2(b).

Все порты, связанные с одним модулем сетевого интерфейса, используют общий набор очередей передачи и представляются операционной системе как один унифицированный интерфейс. Это позволяет переносить потоки из порта в порт, изменяя лишь настройки планировщика передачи без воздействия на остальные части сетевого стека. Динамическое сопоставление выходных очередей с портами, управляемое планировщиком, позволяет исследовать новые протоколы и архитектуру сетей, включая параллельные сети, такие как P-FatTree [16], и сети с оптической коммутацией, такие как RotorNet [17] и Opera [18].

Путь данных и механизмы приёма и передачи

В пути данных Corundum применяются отображаемые на память и потоковые интерфейсы. Поток AXI служит для переноса пакетов Ethernet между модулями DMA порта, Ethernet MAC, а также модулями контрольной суммы (Csum) и расчёта хэш-значения (Hash). Поток AXI служит также для соединения ядра PCIe hard IP с ведущим модулем PCIe AXI lite и модулем интерфейса PCIe DMA. Для подключения интерфейсного модуля PCIe DMA, модулей DMA в портах, а также логики очередей дескрипторов и завершения с внутренней памятью (RAM) используется сегментированный интерфейс с памятью.

Разрядность потоковых интерфейсов AXI определяется требуемой пропускной способностью. Логика ядра пути данных (кроме Ethernet MAC) работает в пользовательском домене синхронизации PCIe с частотой 250 МГц. Поэтому потоковые интерфейсы AXI с ядром PCIe hard IP должны соответствовать разрядности интерфейса аппаратного ядра — 256 битов для PCIe gen 3 x8 и 512 битов для PCIe gen 3 x16. На стороне Ethernet разрядность интерфейса соответствует разрядности интерфейса MAC, если частота 250 МГц не слишком мала для обеспечения нужной пропускной способности. Для Ethernet 10G разрядность интерфейса MAC составляет 64 бита при частоте 156,25 МГц и он может быть соединён с доменом синхронизации 250 МГц той же разрядности. Для Ethernet 25G интерфейс MAC имеет разрядность 64 бита при частоте 390,625 МГц, что требует его преобразования в разрядность 128 битов для обеспечения нужной пропускной способности при частоте 250 МГц. Для Ethernet 100G в Corundum применяются аппаратные ядра CMAC Xilinx 100G на Ultrascale Plus FPGA. Интерфейс MAC имеет разрядность 512 битов при частоте 322,266 МГц и подключён к домену синхронизации 250 МГц с разрядностью 512 битов, поскольку для обеспечения пропускной способности 100 Гбит/с нужна частота около 195 МГц.


Рисунок 3. Упрощённый вариант рисунка с путями данных.

Блок-схема пути данных NIC показана на рисунке 3. Ядро PCIe hard IP (PCIe HIP) соединяет NIC с хостом. Два потоковых интерфейса соединяют интерфейсный модуль PCIe DMA с ядрами PCIe hard IP. Один интерфейс служит для запросов чтения и записи, другой — для чтения данных. Модуль интерфейса PCIe DMA соединяется с модулем выборки дескрипторов, модулем завершения записи, модулями временной памяти (RAM), а также механизмами RX и TX через набор набор мультиплексоров интерфейса DMA. В направлении интерфейса DMA мультиплексоры комбинируют команды переноса DMA от нескольких источников. В обратном направлении они маршрутизируют передачу откликов о состоянии. Мультиплексоры также управляют сегментированными интерфейсами с памятью при чтении и записи. Мультиплексор верхнего уровня объединяет трафик дескрипторов с трафиком пакетов данных, предоставляя дескрипторам более высокий приоритет. Затем пара мультиплексоров объединяет трафик от нескольких интерфейсных модулей. Дополнительный мультиплексор внутри каждого интерфейсного модуля объединяет трафик пакетов данных от нескольких экземпляров портов.

Машины приёма и передачи отвечают за координацию операций при передаче и приёме пакетов. Эти машины могут обслуживать множество обрабатываемых пакетов для повышения пропускной способности. Как показано на рисунке , машины приёма и передачи соединены с несколькими модулями тракта приёма и передачи данных, включая модули DMA, модули выгрузки контрольных сумм и хэширования, логику обработки дескрипторов и завершения, а также а также интерфейсы временных меток Ethernet MAC.

Машина передачи отвечает за координацию операций отправки пакетов. Она обрабатывает запросы передачи от планировщика передачи для конкретных очередей. После низкоуровневой обработки с использованием механизма PCIe DMA пакет проходит через модуль контрольной суммы, MAC и PHY. После отправки пакета машина передачи получает метку PTP от MAC, создаёт запись о завершении и передаёт её модулю завершения записи.

Приёмная машина отвечает за координацию операций получения пакетов. Входящие пакеты проходят через PHY и MAC. После низкоуровневой обработки, включающей хэширование и установку метки времени, машина приёма будет выдавать 1 или несколько запросов на запись машине PCIe DMA для записи данных пакета в память хоста. По завершении записи машина приёма создаёт запись о завершении и передаёт её модулю завершения.

Модули чтения и завершения записи работают аналогично машинам приёма и передачи. Эти модули обрабатывают запросы дескрипторов и завершения чтения/записи от машин приёма и передачи, выдают запросы на размещение в очереди и извлечение из неё диспетчерам очередей для получения адреса элемента очереди в памяти хоста, а также выдают запросы к интерфейсу PCIe DMA для доставки данных. Модуль завершения записи отвечает также за обработку событий в очередях завершения передачи и приёма, путём включения их в подходящую очередь событий и внесения записей о событиях.

Сегментированный интерфейс с памятью

Для обеспечения высокой производительности DMA через PCIe в Corundum применяется сегментированный интерфейс с памятью. Интерфейс расщеплён на сегменты с максимальным размером 128 битов и разрядностью вдвое больше разрядности потокового интерфейса AXI для ядра PCIe hard IP. Например, для PCIe gen 3 x16 с 512-битовым потоковым интерфейсом AXI из аппаратного ядра PCIe будет применяться 1024-битовый сегментированный интерфейс с 8 сегментами по 128 битов. Такой интерфейс обеспечивает лучшее соответствие импеданса при использовании одного интерфейса AXI, что позволяет более полно использовать канал PCIe за счёт устранения противодействия из-за выравнивания с машине DMA и арбитража в логике соединений. В частности, интерфейс гарантирует, что интерфейс DMA может выполнять полноразрядное чтение или запись без выравнивания в каждом такте. Кроме того, использование простого двухпортового ОЗУ, выделенного для трафика одного направления, устраняет противоречия между путями чтения и записи.

Каждый сегмент работает подобно интерфейсу AXI lite, но использует 3 интерфейса вместо 5. Один канал предоставляет адрес и данные для записи, другой — адрес для чтения, третий — считываемые данные. В отличие от AXI, всплески (burst) и переупорядочение не поддерживаются, что упрощает логику интерфейса. Соединительные компоненты (мультиплексоры) отвечают за сохранение порядка операций даже при доступе к нескольким блокам RAM. Сегменты действуют независимо друг от друга с отдельными соединениями для управления потоками данных и экземплярами логики упорядочения соединений. Операции маршрутизируются по сигналам выбора, а не по декодированию адресов. Это избавляет от необходимости назначать адреса и позволяет использовать параметризуемые компоненты соединений, маршрутизирующие операции с минимальной настройкой конфигурации.

Байтовые адреса отображаются на адреса сегментированного интерфейса и младшие биты адреса задают байтовую «дорожку» (lane) в сегменте, следующие — сегмент, а старшие — адрес слова для этого сегмента. Например, в 1024-битовом сегментированном интерфейсе с 8 сегментами по 128 битов 4 младших бита адреса задают «дорожку», следующие 3 — сегмент, а оставшиеся адресную шину для сегмента.

Драйвер устройства

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

NIC использует пространство регистров для раскрытия таких параметров, как число интерфейсов, портов, очередей и планировщиков, размер максимального передаваемого блока (maximum transport unit или MTU), поддержка меток PTP и выгрузки. Драйвер считывает регистры при инициализации, что даёт ему возможность настроить себя и зарегистрировать интерфейсы ядра в соответствии с конфигурацией NIC. Эта возможность автоматического определения означает слабую связь между драйвером и NIC, драйверу обычно не нужно менять тракт данных при работе с разными платами FPGA, вариантами устройства Corundum и значениями параметров.

Литература

[1] Alex Forencich, Alex C. Snoeren, George Porter, George Papen. Corundum: An Open-Source 100-Gbps NIC. 2020 IEEE 28th Annual International Symposium on Field-Programmable Custom Computing Machines (FCCM). https://www.fccm.org/past/2020/proceedings/2020/pdfs/FCCM2020-65FOvhMqzyMYm99lfeVKyl/580300a038/580300a038.pdf, https://github.com/corundum/corundum

[2] D. Firestone, A. Putnam, S. Mundkur, D. Chiou, A. Dabagh, M. Andrewartha, H. Angepat, V. Bhanu, A. Caulfield, E. Chung, H. K. Chandrappa, S. Chaturmohta, M. Humphrey, J. Lavier, N. Lam, F. Liu, K. Ovtcharov, J. Padhye, G. Popuri, S. Raindel, T. Sapre, M. Shaw, G. Silva, M. Sivakumar, N. Srivastava, A. Verma, Q. Zuhair, D. Bansal, D. Burger, K. Vaid, D. A. Maltz, and A. Greenberg, “Azure accelerated networking: SmartNICs in the public cloud,” in 15th USENIX Symposium on Networked Systems Design and Implementation (NSDI 18). Renton, WA: USENIX Association, Apr. 2018, pp. 51-66. https://www.usenix.org/conference/nsdi18/presentation/firestone

[3] B. Stephens, A. Akella, and M. M. Swift, “Your programmable NIC should be a programmable switch,” in Proceedings of the 17th ACM Workshop on Hot Topics in Networks, ser. HotNets ’18. New York, NY, USA: Association for Computing Machinery, 2018, p. 36-42. https://www2.cs.uic.edu/~brents/docs/panic.hotnets18.pdf

[4] S. Radhakrishnan, Y. Geng, V. Jeyakumar, A. Kabbani, G. Porter, and A. Vahdat, “SENIC: Scalable NIC for end-host rate limiting,” in 11th USENIX Symposium on Networked Systems Design and Implementation (NSDI 14). Seattle, WA: USENIX Association, 2014, pp. 475-488. https://www.usenix.org/system/files/conference/nsdi14/nsdi14-paper-radhakrishnan.pdf

[5] A. Saeed, N. Dukkipati, V. Valancius, V. The Lam, C. Contavalli, and A. Vahdat, “Carousel: Scalable traffic shaping at end hosts,” in Proceedings of the Conference of the ACM Special Interest Group on Data Communication, ser. SIGCOMM ’17. New York, NY, USA: Association for Computing Machinery, 2017, p. 404-417. https://www.comm.utoronto.ca/~jorg/teaching/ece1545/papers/Carousel-Sigcomm2017-paper.pdf

[6] M. Handley, C. Raiciu, A. Agache, A. Voinescu, A. W. Moore, G. Antichi, and M. Wójcik, “Re-architecting datacenter networks and stacks for low latency and high performance,” in Proceedings of the Conference of the ACM Special Interest Group on Data Communication, ser. SIGCOMM ’17. New York, NY, USA: Association for Computing Machinery, 2017, p. 29-42. [Online]. https://discovery.ucl.ac.uk/id/eprint/10068163/1/ndp.pdf

[7] B. Stephens, A. Akella, and M. Swift, “Loom: Flexible and efficient NIC packet scheduling,” in 16th USENIX Symposium on Networked Systems Design and Implementation (NSDI 19). Boston, MA: USENIX Association, Feb. 2019, pp. 33-46. https://www.usenix.org/system/files/nsdi19-stephens.pdf

[8] Data plane development kit, https://www.dpdk.org/

[9] Y. Li, R. Miao, H. H. Liu, Y. Zhuang, F. Feng, L. Tang, Z. Cao, M. Zhang, F. Kelly, M. Alizadeh, and et al., “HPCC: High precision congestion control,” in Proceedings of the ACM Special Interest Group on Data Communication, ser. SIGCOMM ’19. New York, NY, USA: Association for Computing Machinery, 2019, p. 44-58. https://liyuliang001.github.io/publications/hpcc.pdf

[10] Cisco Nexus SmartNIC https://www.cisco.com/c/en/us/products/interfaces-modules/nexus-smartnic/index.html

[11] Magmio https://www.magmio.com/product

[12] Atomic rules, http://www.atomicrules.com/

[13] N. Zilberman, Y. Audzevich, G. A. Covington, and A. W. Moore, “NetFPGA SUME: Toward 100 Gbps as research commodity,” IEEE Micro, vol. 34, no. 5, pp. 32-41, Sep. 2014. https://github.com/NetFPGA/netfpga.git, https://www.cl.cam.ac.uk/~nz247/publications/zilberman2014sume.pdf

[14] A. M. Caulfield, E. S. Chung, A. Putnam, H. Angepat, J. Fowers, M. Haselman, S. Heil, M. Humphrey, P. Kaur, J.-Y. Kim, and et al., “A cloud-scale acceleration architecture,” in The 49th Annual IEEE/ACM International Symposium on Microarchitecture, ser. MICRO-49. IEEE Press, 2016. https://www.cs.utexas.edu/ftp/dburger/papers/MICRO16.pdf

[15] S. Pontarelli, R. Bifulco, M. Bonola, C. Cascone, M. Spaziani, V. Bruschi, D. Sanvito, G. Siracusano, A. Capone, M. Honda, F. Huici, and G. Siracusano, “FlowBlaze: Stateful packet processing in hardware,” in 16th USENIX Symposium on Networked Systems Design and Implementation (NSDI 19). Boston, MA: USENIX Association, Feb. 2019, pp. 531-548. https://www.usenix.org/conference/nsdi19/presentation/pontarelli

[16] W. M. Mellette, A. C. Snoeren, and G. Porter, “P-FatTree: A multi-channel datacenter network topology,” in Proceedings of the 15th ACM Workshop on Hot Topics in Networks, ser. HotNets ’16. New York, NY, USA: Association for Computing Machinery, 2016, p. 78-84. https://cseweb.ucsd.edu//~snoeren/papers/ptree-hotnets16.pdf

[17] W. M. Mellette, R. McGuinness, A. Roy, A. Forencich, G. Papen, A. C. Snoeren, and G. Porter, “RotorNet: A scalable, low-complexity, optical datacenter network,” in Proceedings of the Conference of the ACM Special Interest Group on Data Communication, ser. SIGCOMM ’17. New York, NY, USA: Association for Computing Machinery, 2017, p. 267-280. https://cseweb.ucsd.edu/~gmporter/papers/sigcomm17-rotornet.pdf

[18] W. M. Mellette, R. Das, Y. Guo, R. McGuinness, A. C. Snoeren, and G. Porter, “Expanding across time to deliver bandwidth efficiency and low latency,” in 17th USENIX Symposium on Networked Systems Design and Implementation (NSDI 20). Santa Clara, CA: USENIX Association, Feb. 2020. https://www.usenix.org/system/files/nsdi20-paper-mellette.pdf

[19] V. Shrivastav, “Fast, scalable, and programmable packet scheduler in hardware,” in Proceedings of the ACM Special Interest Group on Data Communication, ser. SIGCOMM ’19. New York, NY, USA: Association for Computing Machinery, 2019, p. 367-379. https://web.ics.purdue.edu/~vshriva/papers/pieo_2019.pdf


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

nmalykh@protokols.ru


1Data Plane Development Kit — комплект для разработки плоскости данных.

2Приобретена компанией Cisco Systems.

3Time Division Multiple Access — множественный доступ с разделением по времени.

4Узел преобразования параллельных данных (шина) в последовательные (поток).

Рубрика: Linux, Физические интерфейсы | Оставить комментарий

SNSD

NTK_RFC 0009

Scattered Name Service Disgregation

Распределенная служба имен

PDF

В этом документе описана распределенная служба имен (Scattered Name Service Disgregation) — расширение протокола ANDNA. Текст будет включен в окончательную документацию, а сейчас в него можно вносить правки, связавшись с разработчиками.

SNSD

Распределенная служба имен SNSD является эквивалентом ANDNA для записи SRV Record в Internet DNS1, определенной в [1] и кратко описанной в [2].

SNSD отличается от SRV Record и имеет свои уникальные свойства.

С помощью SNSD можно связать адреса IP и имена хостов с другим hostname.

Каждая назначенная запись имеет номер службы (service number), что позволяет группировать адреса IP и hostname с одинаковым номером в массив. При запросе распознавания (resolution request) клиент будет указывать номер, поэтому он получит запись для указанного номера, связанного с hostname.

Например, узел X имеет зарегистрированное имя хоста angelica и по умолчанию для этого имени выделен IP-адрес 1.2.3.4. X связывает имя хоста depausceve со службой http (номер 80) для имени хоста angelica, а также адрес 11.22.33.44 со службой ftp (номер 21) хоста angelica.

Когда Y обычным путем распознает адрес angelica, он получает 1.2.3.4, но при распознавании адреса из web-браузера он будет запрашивать запись, связанную со службой http, и запрос будет возвращать depausceve. Браузер будет распознавать адрес по имени depausceve и получит доступ к серверу. Когда клиент ftp хоста Y будет пытаться распознать адрес angelica, он получит 11.22.33.44. Если Y попытается распознать адрес для не связанной службы, он получит основной адрес хоста 1.2.3.4.

Узел, связанный с записью SNSD, называется узлом SNSD (SNSD node). В приведенном примере такими узлами являются depausceve и 11.22.33.44.

Узел, который регистрирует записи и сохраняет регистрацию основного имени хоста, всегда называют регистрирующим узлом (register node), но может также использоваться термин Zero SNSD node — фактически это соответствует наиболее общей записи SNSD для службы с номером 0.

Отметим, что SNSD полностью отменяет NTK_RFC 0004 [3].

Служба, приоритет, вес

Номер службы

Номер службы указывает область действия записи SNSD. Адрес IP, связанный со службой номер x, будет возвращаться только в ответ на запросы распознавания, содержащие этот номер. Номер службы — это номер порта, с которым связана соответствующая служба. Номера портов можно узнать из файла /etc/services. Служба с номером 0 соответствует обычной записи ANDNA и в ответ на базовый запрос распознавания будет возвращаться соответствующий адрес IP.

Приоритет

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

Вес

Значение веса, связанное с записью SNSD, служит для выбора среди нескольких записей с одинаковым приоритетом. Клиент запрашивает у ANDNA распознавание и получает, например, 8 разных записей. Первая запись, используемая клиентом, выбирается псевдослучайным способом. Каждая запись может быть выбрана с вероятностью, пропорциональной ее весу. Отметим, что в случае одинакового приоритета у всех записей выбор становится полностью случайным.

Для запрета использования записи можно задать нулевой вес. Значения веса должны быть меньше 128.

Регистрация SNSD

Метод регистрации записи SNSD похож на описанный в [3]. С одной службой можно связать до 16 записей. Максимальное число зарегистрированных записей составляет 256.

Регистрация записей SNSD выполняется одним узлом register_node. Узел hash_node, получающий регистрацию, не будет связываться с counter_node, поскольку имя хоста уже зарегистрировано и его не нужно проверять. Остается проверить лишь действительность подписи.

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

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

 * Изменение файла /etc/netsukuku/snsd_nodes.
{{{
register_node# cd /etc/netsukuku/ 
register_node# cat snsd_nodes
#
# Файл узлов SNSD
#
# В формате
# hostname:snsd_hostname:service:priority:weight[:pub_key_file]
# или
# hostname:snsd_ip:service:priority:weight[:pub_key_file]
#
# Параметр pub_key_file является необязательным. При его наличии NetsukukuD будет
# периодически проверять snsd_hostname и контролировать принадлежность имени к
# одной машине. При смене машины соответствующая запись snsd будет удаляться.
#

depausceve:pippo:http:1
depausceve:1.2.3.4:21:0

angelica:frenzu:ssh:1:/etc/netsukuku/snsd/frenzu.pubk

register_node#
register_node# scp frenzu:/usr/share/andna_lcl_keyring snsd/frenzu.pubk
}}}
 * Передача сигнала SIGHUP демону NetsukukuD на регистрирующем узле.
{{{
register_node# killall -HUP ntkd
# или 
register_node# rc.ntk reload
}}}

Нулевое значение SNSD IP

Основной адрес IP, связанный с обычным hostname, имеет по умолчанию приведенные ниже значения.

{{{
IP       = register_node IP     # Это значение нельзя изменить
service  = 0
priority = 16
weight   = 1
}}}

Можно связать другие записи SNSD со службой 0, но не разрешается менять основной адрес IP, в качестве которого может служить только IP-адрес узла register_node. Хотя нет возможности установить другую привязку для основного адреса IP, его можно «отключить», установив вес 0.

Строка, применяемая для смены значений веса и приоритета основного IP, имеет вид

{{{
hostname:hostname:0:priority:weight

# Например,
register_node# echo depausceve:depausceve:0:23:12 >> /etc/netsukuku/snsd_nodes
}}}

Цепочка SNSD

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

{{{
depausceve registers: depausceve:80 --> pippo
pippo registers:      pippo:0  --> frenzu
frenzu registers:     frenzu:0 --> angelica
}}}

Однако цепочки SNSD игнорируются и пригодным считается лишь первое преобразование. Поскольку для службы 0 всегда имеется основной адрес IP, распознавание выполняется всегда. В приведенном случае (depausceve:80 —> pippo:0) распознавание будет возвращать основной IP-адрес pippo:0.

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


[1] http://www.ietf.org/rfc/rfc2782.txt

[2] http://en.wikipedia.org/wiki/SRV_record

[3] Mail Exchange request, http://netsukuku.freaknet.org/main_doc/ntk_rfc/Ntk_MX_request

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

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

nmalykh@protokols.ru

1Domain Name System — служба доменных имен.

Рубрика: Фрактальные сети | Комментарии к записи SNSD отключены

Протокол ANDNS

PDF

0. Введение

В этом документе описан протокол, применяемый для коммуникаций с ANDNA. Протокол используется для запросов в сфере internet. Например, можно запросить google.it в internet или depausceve в сети netsukuku.

В случае запросов internet элемент dns_wrapper будет взаимодействовать с серверами dns, заданными в файле /etc/resolv.conf, когда модуль ntkd загружен.

1. Обозначения

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

         1  2  3  4  5  6  7  8
        +--+--+--+--+--+--+--+--+
        |                       |
        +--+--+--+--+--+--+--+--+

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

         1  2  3  4  5  6  7  8  1  2  3  4  5  6  7  8
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                       |                       |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

2. Заголовки

Заголовки имеют размер 4 байта и показанный на рисунке формат

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                      ID                    | R|
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |QR| P| Z| QT  |  ANCOUNT  |I |   NK|   RCODE   |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

ID

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

R

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

QR

0 для вопросов, 1 для ответов.

P

Если запрос является h2ip и связан с ntk, P задает протокол: 0 для TCP, 1 для UDP и 0 в остальных случаях.

Z

Сжатие zlib. При z=1 содержимое пакета (кроме заголовков) сжимается с помощью zlib (см. 8. Сжатие).

QT

Тип запроса (см. 3. Типы запросов). В откликах это поле должно сохраняться неизменным.

ANCOUNT

Счетчик ответов. Поле устанавливается только при QR=1 (пакет содержит ответы) и указывает число ответов в этом пакете.

I

Бит версии IP, использованной для содержимого пакета. Все адреса в пакете являются IPv4 (4 байта), если I=0 и IPv6 (16 байтов), если I=1. Этот бит полезен лишь в вопросах. Сервер будет возвращать ответ NO SUCH DOMAIN (нет такого домена), если на его узле применяется иная версия протокола IP. При совпадении версий в ответе будет использоваться та же версия протокола.

NK

Биты Netsukuku, позволяющий задать область запроса (query realm). При NK=1 областью запроса является netsukuku, при NK=2 — internet. Значение NK=0 указывает, что пакет не кодируется данным протоколом и содержит обычный протокол DNS (см. 4. Область запроса). В ответах это поле должно сохраняться.

RCODE

Результат запроса. Для пакетов с вопросами устанавливается RCODE = 0. В случае ошибок устанавливается ANCOUNT = 0.

3. Типы запросов

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

QTYPE = 0

Это классическое преобразование hostname -> ip (gethostbyname). Этот тип запросов применяется также для распознавания SNSD [1]. Можно указать сервис и общая форма представления такого запроса имеет вид

                hostname:service -> ip

Если сервис не указан, будет применяться 0-service.

Например, если нужно найти адрес хостинга сервиса http хоста depausceve, запрос может иметь вид

                depausceve:80

Формирование запросов описано в 6. Вопросы.

QTYPE = 1

Обратное преобразование ip -> host.

QTYPE =2

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

4. Область запроса

Запрос можно сформулировать для поиска того или иного объекта в сети netsukuku или internet. При использовании протокола ANDNS область запроса указывается битами NK (см. 2. Заголовки).

Если используется протокол DNS, нужно формулировать запрос с неким суффиксом. Если запрос сделан для google.it.int (или google.it.INT), он будет относиться к internet. Запрос google.it.ntk (или google.it.NTK) будет относиться к сети netsukuku. Если суффикс не задан, по умолчанию областью запроса служит Netsukuku

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

5. RCODE

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

RCODE = 0 No Error

Пакет содержит ответы (ANSWERS), число которых указывается полем ANCOUNT (см. 2. Заголовки).

RCODE = 1 Interpretation Error

Сервер не понял запроса (запрос сформирован некорректно).

RCODE = 2 Server Fail

Сервер столкнулся с ошибкой при обработке корректного запроса.

RCODE = 3 No Such Domain

Искомого объекта не существует.

RCODE = 4 Not Implemented

Данный тип запросов не реализован на этом сервере.

RCODE = 5 Refused

Сервер отказывается взаимодействовать с вами.

Отметим, что выражение (RCODE XOR ANCOUNT) всегда истинно. Если RCODE содержит ту или иную ошибку (RCODE!=0), в пакете не будет ответа. Если же RCODE = 0 (нет ошибок), пакет содержит тот или иной ответ.

6. Вопросы

Формат вопроса зависит от QTYPE.

Case QTYPE = 0 (h2ip) и Realm=NTK

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    SERVICE                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |       
        |                                               |       
        |                                               |
        |                   HOSTNAME                    |
        |                     HASH                      |
        |                     (HH)                      |
        |                                               |
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

SERVICE — 2-байтовое поле, представляющее значение сервиса SNSD [1].

HH — 16-байтовое хэш-значение имени хоста (hostname).

QTYPE = 0 (h2ip) и Realm=INET

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                   SERVICE                     |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                   RDLENGTH                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |
        \                                               \
        \                    RDATA                      \
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

SERVICE — 2-байтовое поле, представляющее значение сервиса SNSD [1]. В данный момент для преобразований INET сервис ограничен значением 25 (предполагается TCP) или 0.

RDLENGTH — размер RDATA.

RDATA — строка имени хоста с учетом strlen(RDATA)=RDLENGTH.

QTYPE = 1 (ip2h)

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |
        /                     RDATA                     /
        /                                               /
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

RDATA — адрес IP в двоичном формате. Размер поля (4 или 16 байтов) зависит от поля I в заголовке.

QTYPE = 2 (глобальный запрос)

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |       
        |                                               |       
        |                                               |
        |                   HOSTNAME                    |
        |                     HASH                      |
        |                     (HH)                      |
        |                                               |
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

HH — 16-байтовое хэш-значение имени хоста (hostname).

7. Ответы

QTYPE=0 (h2ip)

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |  | T|     WG          |       PRIO            |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |
        /                     RDATA                     /
        /                                               /
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Бит T устанавливается (1), если ответ содержит IP. T = 0 говорит о том, что ответ содержит хэш hostname.

WG указывает «вес» ответа [1].

PRIO — приоритет [1].

RDATA содержит двоичный адрес IP, размер которого определяется битом I в заголовке, или хэш hostname.

QTYPE=1 (ip2h)

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    RDLENGTH                   |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |
        /                     RDATA                     /
        /                                               /
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

RDLENGTH — размер RDATA (RDATA — имя хоста).

RDATA — распознанное имя хоста.

QTYPE=2 (глобальный запрос)

При QTYPE=2 перед ответом помещается дополнительное поле заголовка

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    ANCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Эти 2 байта указывают число ответов, порожденных запросом. Отметим, что поле ANCOUNT в основном заголовке будет иметь значение 1, если RCODE=0, и 0 в противном случае. Эти два байта указывают реальное число ответов для случая QTYPE=2.

После дополнительного заголовка размещаются ответы в показанном на рисунке формате.

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        | M| T| P|  WG          |       PRIO            |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    SERVICE                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |
        /                     DATA                      /
        /                                               /
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Бит T указывает тип DATA (T для Hostname и 1 для IP).

Бит M устанавливается при установленном бите T и показывает, что это MAIN_IP для hostname.

P — протокол (0 для TCP, 1 для UDP).

При T=1 версия IP указывается в основном заголовке. Если T=0, данные имеют формат

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |       
        |                                               |       
        |                                               |
        |                   HOSTNAME                    |
        |                     HASH                      |
        |                     (HH)                      |
        |                                               |
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

HH — 16-байтовое хэш-значение SNSD hostname.

При T=1 данные имеют показанный на рисунке формат

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |
        /                    RDATA                      /
        /                                               /
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

RDATA — двоичное представление IP, размер которого зависит от бита I в основном заголовке (4 для IPv4, 16 для IPv6).

8. Сжатие

Формат сжатого пакета показан на рисунке.

        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                      ID                    | R|
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |QR| P| Z| QT  |  ANCOUNT  |I |   NK|   RCODE   |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                     USIZE                     |
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                                               |
        /                     DATA                      /
        /                                               /
        |                                               |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Заголовки остаются не сжатыми. Содержимое пакета, вопрос и ответы сжимаются с помощью zlib. Буфером, для сжатия является DATA. Поле USIZE показывает исходный размер содержимого пакета (вопрос и ответы). Для сжатого пакета устанавливается Z=1.

[1] NTC_RFC 0009 http://netsukuku.freaknet.org/main_doc/ntk_rfc/Ntk_SNSD

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

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

nmalykh@protokols.ru

Рубрика: Фрактальные сети | Комментарии к записи Протокол ANDNS отключены

RFC 8684 TCP Extensions for Multipath Operation with Multiple Addresses

Internet Engineering Task Force (IETF)                           A. Ford
Request for Comments: 8684                                         Pexip
Obsoletes: 6824                                                C. Raiciu
Category: Standards Track                    U. Politehnica of Bucharest
ISSN: 2070-1721                                               M. Handley
                                                       U. College London
                                                          O. Bonaventure
                                                U. catholique de Louvain
                                                               C. Paasch
                                                             Apple, Inc.
                                                              March 2020

TCP Extensions for Multipath Operation with Multiple Addresses

Расширения TCP для работы по нескольким путям с множеством адресов

PDF

Аннотация

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

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

Документ задает протокол Multipath TCP v1, отменяя предыдущую версию (v0), определенную в RFC 6824, разъясняя и меняя некоторые свойства на основе опыта развертывания.

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

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

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

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

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

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).

1. Введение

Multipath TCP (MPTCP) представляет собой набор расширения протокола TCP [RFC0793], обеспечивающих услуги Multipath TCP [RFC6182], когда транспортное соединение может использовать одновременно множество путей. В документе представлены изменения, которые нужны для поддержки множества путей в TCP, в частности, для сигнализации и организации множества путей (субпотоков), управления этими субпотоками, сборки данных и завершения сессий. Однако документ включает не только информацию, требуемую для реализации Multipath TCP, но и дополняет три других документа, указанных ниже.

  • [RFC6182] (архитектура MPTCP), разъясняющий мотивы разработки Multipath TCP и обсуждающий устройство протокола на высоком уровне, а также разъясняющий функциональное деление, которое может быть реализовано в расширяемом MPTCP.

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

  • [RFC6897] (взаимодействие с приложением), где обсуждается влияние MPTCP на приложения, ожидания приложений от MPTCP и последствия этих факторов, а также расширения API для MPTCP.

Этот документ отменяет спецификацию Multipath TCP v0 [RFC6824] и задает спецификацию MPTCP v1, не совместимую с MPTCP v0. Кроме того, документ определяет процедуру согласования версии для реализаций, поддерживающих оба варианта.

1.1. Допущения

Для ограничения пространства проектирования рабочая группа MPTCP внесла два важных ограничения в протокол Multipath TCP, представленный в этом документе:

  • требуется совместимость с обычным TCP для упрощения развертывания в сети;

  • можно предполагать, что один или оба хоста являются многодомными и многоадресными.

Для упрощения предполагается, что наличия на хосте множества адресов достаточно для индикации существования множества путей. Эти пути не обязаны быть полностью разными и допускается совместное использование одного или нескольких маршрутизаторов разными путями. Даже в такой ситуации использование множества путей обеспечивает преимущества, улучшая использование ресурсов и повышение устойчивости к некоторым отказам узлов. Алгоритмы контроля перегрузок, определенные в [RFC6356], обеспечивают отсутствие вредных воздействий при использовании множества путей. Кроме того, могут возникать ситуации, когда несколько портов TCP на одном хосте могут предоставлять разные пути (например, при использовании некоторых реализаций ECMP3 [RFC2992]), поэтому MPTCP поддерживает идентификацию путей по портам.

Для обеспечения упомянутой выше совместимости важны три аспекта, подробно рассмотренных в [RFC6182].

Внешние ограничения

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

Ограничения приложение

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

Возврат к TCP

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

Дополнительное рассмотрение взаимодействия с приложениями в [RFC6897] указывает требуемые функции API для обеспечения совместимости со старыми версиями, а также расширения API, обеспечивающие работу MPTCP на уровнях управления и данных, эквивалентную обычному TCP с одним путем.

Дополнительное рассмотрение ограничений и связанных с ними решений в части архитектуры MPTCP приведено в [RFC6182] и [howhard].

1.2. Multipath TCP в сетевом стеке

MPTCP работает на транспортном уровне и стремиться быть прозрачным для вышележащего и нижележащего уровня. Протокол добавляет ряд свойств к стандартному TCP. Уровни протоколов показаны на рисунке 1. MPTCP разработан для использования унаследованными приложениями без их изменения. Этот вопрос подробно рассматривается в [RFC6897].


                             +-------------------------------+
                             |           Приложение          |
+---------------+            +-------------------------------+
|  Приложение   |            |             MPTCP             |
+---------------+            + - - - - - - - + - - - - - - - +
|      TCP      |            | Субпоток (TCP)| Субпоток (TCP)|
+---------------+            +-------------------------------+
|      IP       |            |       IP      |      IP       |
+---------------+            +-------------------------------+

Рисунок 1. Сравнение стеков TCP и MPTCP.

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

В этом документе используется множество терминов, относящихся к MPTCP или определенных в контексте MPTCP.

Path — путь

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

Subflow — субпоток

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

(Multipath TCP) Connection — соединение (Multipath TCP)

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

Data-level — уровень данных

Данные (payload), номинально передаваемые через соединение (в субпотоках). Термины «уровень данных» и «уровень соединения» относятся к соединению в целом, а «уровень субпотока» — к отдельному субпотоку.

Token — маркер

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

Host — хост

Конечный хост с реализацией MPTCP, который инициирует или воспринимает соединение MPTCP.

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

1.4. Концепции MPTCP

В этом параграфе представлен высокоуровневый обзор работы MPTCP. Типичный вариант применения протокола показан на рисунке 2. Подробное рассмотрение работы MPTCP приведено в разделе 3.

  •          Хост A                               Хост B
    ------------------------             ------------------------
     Адрес A1      Адрес A2               Адрес B1      Адрес B2
    ----------    ----------             ----------    ----------
        |             |                      |             |
        |  (начальная организация соедин.)   |             |
        |----------------------------------->|             |
        |<-----------------------------------|             |
        |             |                      |             |
        |            (доп. организ. субпотока)             |
        |             |--------------------->|             |
        |             |<---------------------|             |
        |             |                      |             |

    Рисунок 2. Пример использования MPTCP.


    Для приложений, не понимающих MPTCP протокол будет вести себя как обычный TCP. Расширенные API могут обеспечивать дополнительный контроль для приложений MPTCP [RFC6897]. Приложение начинает работу с обычного создания сокета TCP, а сигнализация и работа MPTCP обеспечиваются реализацией.

  • Соединение MPTCP начинается подобно обычному соединению TCP. Это показано на рисунке 2, где соединение MPTCP организуется между адресами A1 и B1 на хостах A и B.

  • При наличии дополнительных путей открываются новые сессии TCP (субпотоки MPTCP) и объединяются с имеющейся сессией для организации одного соединения между приложениями на двух сторонах. Создание дополнительной сессии между адресами A2 хоста Host A и B1 хоста B показано на рисунке 2.

  • MPTCP идентифицирует наличие множества путей по присутствию на хосте множества адресов. Комбинации этих адресов приравниваются к дополнительным путям. В приведенном выше примере дополнительными путями могут быть A1<->B2 и A2<->B2. Хотя эти сессии показаны как инициированные с адреса A2, их можно инициировать также с адреса B1 или B2.

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

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

  • Субпотоки завершаются как обычные соединения TCP с помощью 4-этапного согласования FIN. Соединение MPTCP прерывается FIN на уровне соединения.

1.5. Уровни требований

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

2. Обзор работы протокола

В этом разделе представлено описание работы MPTCP со ссылками на протокольные операции. Это высокоуровневый обзор основных функций, а полная спецификация приведена в разделе 3. Расширяемый и согласуемый набор функций не рассматривается здесь. В этом разделе часто упоминаются символьные имена опций MPTCP, которые относятся к субтипам выделенной IANA опции MPTCP (раздел 7), а их форматы определены в спецификации протокола (раздел 3).

Соединение Multipath TCP поддерживает двухсторонний поток байтов между двумя хостами, взаимодействующими как при обычном TCP, и в приложения не требуется вносить какие-либо изменения. Однако Multipath TCP позволяет хостам использовать разные адреса IP для обмена пакетами, относящимися к одному соединению MPTCP. Соединение Multipath TCP выглядит для приложения подобно обычному соединению TCP, однако для сетевого уровня каждый субпоток MPTCP похож на обычный поток TCP, сегменты которого имеют новый тип опций TCP. Multipath TCP управляет созданием, удалением и использованием этих субпотоков для передачи данных. Число субпотоков, поддерживаемых в соединении Multipath TCP, не фиксировано и может меняться в процессе работы соединения.

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

2.1. Инициирование соединения MPTCP

Здесь применяется такая же сигнализация как в соединениях TCP но пакеты SYN, SYN/ACK и начальный ACK (и данные) включают также опцию MP_CAPABLE, которая имеет переменный размер и служит нескольким целям. Во-первых, проверяется поддержка Multipath TCP удаленным хостом, во-вторых, опция позволяет хостам обменяться данными для аутентификации при создании дополнительных потоков. Полное описание приведено в параграфе 3.1.

      Хост A                                  Хост B
      ------                                  ------
      MP_CAPABLE                 ->
      [флаги]
                                 <-           MP_CAPABLE
                                              [ключ B, флаги]
      ACK + MP_CAPABLE (+ данные)->
      [ключ A, ключ B, флаги, (детали уровня данных)]

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

      Хост A (с данными для передачи сразу)   Хост B
      ------                                  ------
      MP_CAPABLE                ->
      [флаги]
                                <-            MP_CAPABLE
                                              [ключ B, флаги]
      ACK + MP_CAPABLE + данные ->
      [ключ A, ключ B, флаги, детали уровня данных]

      Хост A (с данными для передачи позднее) Хост B
      ------                                  ------
      MP_CAPABLE                ->
      [флаги]
                                <-            MP_CAPABLE
                                              [ключ B, флаги]
      ACK + MP_CAPABLE          ->
      [ключ A, ключ B, флаги]

      ACK + MP_CAPABLE + данные ->
      [ключ A, ключ B, флаги, детали уровня данных]

      Хост A                                  Хост B (передает первым)
      ------                                  ------
      MP_CAPABLE                ->
      [флаги]
                                <-            MP_CAPABLE
                                              [ключ B, флаги]
      ACK + MP_CAPABLE          ->
      [ключ A, ключ B, флаги]
                                <-            ACK + DSS + data
                                              [детали уровня данных]

2.2. Связывание нового субпотока с соединением MPTCP

Обмен ключами в согласовании MP_CAPABLE обеспечивает материал, который может служить для проверки подлинности конечных точек при организации новых субпотоков. Дополнительные субпотоки начинаются как обычные соединения TCP, но пакеты SYN, SYN/ACK, ACK включают также опцию MP_JOIN.

Хост A инициирует новый субпоток между одним из своих адресов и адресом хоста B. Маркер, созданный из ключа, служит для идентификации соединения MPTCP, к которому добавляется субпоток, а код HMAC4для аутентификации. HMAC использует ключи, переданные при согласовании MP_CAPABLE и случайные значения (nonce) переданные в опциях MP_JOIN. Опция MP_JOIN включает также флаги и Address ID, который можно использовать для указания адреса источника без необходимости отправителю знать о его возможном изменении транслятором NAT. Более подробное описание дано в параграфе 3.2.

      Хост A                                  Хост B
      ------                                  ------
      MP_JOIN               ->
      [маркер B, nonce A,
       Address ID A, флаги]
                            <-                MP_JOIN
                                              [HMAC B, nonce B,
                                              Address ID B, флаги]
      ACK + MP_JOIN         ->
      [HMAC A]
                            <-                ACK

2.3. Информирование другого хоста о возможных адресах

Набор адресов IP, связанных с многодомным хостом, может меняться в течение срока действия соединения MPTCP. Протокол MPTCP поддерживает добавление или удаление адресов на хосте в явной и неявной форме. Если хост A организовал субпоток со стартовой парой «адрес-порт» IP#-A1 и хочет создать второй субпоток с парой IP#-A2, он просто инициирует организацию субпотока, как описано выше. Удаленный хост получит информацию о новом адресе в неявной форме.

В некоторых обстоятельствах хост может захотеть анонсировать удаленному хосту доступность своих адресов без организации субпотока, например, в случаях, когда NAT препятствует организации соединений в одном из направлений. В приведенном ниже примере хост A информирует хост B о дополнительной паре «адрес-порт» (IP#-A2). Хост B позднее передает опцию MP_JOIN для этого адреса. Опция ADD_ADDR содержит HMAC для аутентификации адреса как отправленного инициатором соединения. Получатель этой опции возвращает ее клиенту для индикации успешного приема. Более подробное описание дано в параграфе 3.4.1.

      Хост A                                 Хост B
      ------                                 ------
      ADD_ADDR                  ->
      [Echo-flag=0,
       IP#-A2,
       Address ID IP#-A2,
       HMAC IP#-A2]
                                <-          ADD_ADDR
                                            [Echo-flag=1,
                                             IP#-A2,
                                             Address ID IP#-A2,
                                             HMAC IP#-A2]

Имеется соответствующий сигнал для удаления адреса с использованием Address ID из согласования ADD_ADDR. Более подробное описание дано в параграфе 3.4.2.

      Хост A                                 Хост B
      ------                                 ------
      REMOVE_ADDR               ->
      [Address ID IP#-A2]

2.4. Передача данных с использованием MPTCP

Для обеспечения надежной и упорядоченной доставки потока данных через субпотоки, которые могут появляться и исчезать в любой момент, MPTCP использует 64-битовый порядковый номер данных (Data Sequence Number или DSN) для нумерации всех данных, передаваемых через соединение MPTCP. Каждый субпоток имеет свое 32-битовое пространство номеров, используя для них стандартное поле заголовка TCP, а опция MPTCP отображает пространство номеров субпотока на пространство порядковых номеров данных. Таким образом, данные можно передавать повторно в другом субпотоке (с отображением на тот же DSN) при возникновении отказа.

Сигнал последовательности данных (Data Sequence Signal или DSS) переносит отображение последовательности данных (Data Sequence Mapping), состоящее из порядкового номера в субпотоке, номера данных и размера, для которого действительно отображение. Эта опция может также включать подтверждение на уровне соединения (Data ACK) для полученного DSN.

В MPTCP все субпотоки используют общий буфер приема и анонсируют одинаковое приемное окно. MPTCP использует подтверждения двух типов — обычные подтверждения TCP используются в каждом субпотоке при получении отправленных в этот субпоток сегментов независимо от DSN и дополнительные подтверждения для пространства номеров данных. Эти подтверждения отслеживают перемещение потока байтов и сдвигают окно приема.

Более подробное описание дано в параграфе 3.3.

      Хост A                                 Хост B
      ------                                 ------
      DSS                       ->
      [Data Sequence Mapping]
      [Data ACK]
      [Checksum]

2.5. Запрос смены приоритета путей

При организации субпотока хосты могут указать его назначение — основной или резервный путь (включается при отсутствии основных). Во время соединения хост A может запросить изменение приоритета субпотока, передав хосту B сигнал MP_PRIO. Более подробное описание дано в параграфе 3.3.8.

      Хост A                                 Хост B
      ------                                 ------
      MP_PRIO                   ->

2.6. Закрытие соединения MPTCP

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

Когда хост A хочет уведомить хост B об отсутствии данных для передачи, он передает Data FIN как часть DSS (см. выше). Семантика и поведение сигнала не отличаются от обычного TCP FIN, но применяются на уровне соединения. После успешного приема всех данных в соединении MPTCP это сообщение подтверждается на уровне соединения с помощью Data ACK. Более подробное описание дано в параграфе 3.3.3.

      Хост A                                 Хост B
      ------                                 ------
      DSS                       ->
      [Data FIN]
                                <-           DSS
                                             [Data ACK]

Имеется дополнительный метод закрытия соединений, названный Fast Close, который похож на закрытие обычного соединения TCP сигналом RST. Сигнал MP_FASTCLOSE служит для индикации партнеру внезапного завершения соединения и прекращения восприятия данных. Это можно использовать в ACK (надежная сигнализация) или RST (без гарантии), как показано ниже. Более подробное описание дано в параграфе 3.5.

      Хост A                                 Хост B
      ------                                 ------
      ACK + MP_FASTCLOSE          ->
      [ключ B]
      [RST во всех других субпотоках] ->
                                  <-         [RST во всех субпотоках]

      Хост A                                 Хост B
      ------                                 ------
      RST + MP_FASTCLOSE          ->
      [ключ B] [во всех субпотоках]
                                  <-         [RST во всех субпотоках]

2.7. Примечательные особенности

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

  • Для работы через трансляторы NAT в пути адреса указываются идентификаторами Address ID в случаях, где IP-адрес отправителя пакета меняется в NAT. Организация нового потока TCP невозможна, если получатель SYN находится за NAT и для организации субпотоков, когда одна сторона расположена за NAT, в MPTCP применяется сообщение ADD_ADDR.

  • MPTCP возвращается к обычному TCP при невозможности работы, например, когда один из хостов не поддерживает MPTCP или промежуточное устройство меняет данные в пакете (см. параграф 3.7).

  • Для устранения угроз, указанных в [RFC6181], ключи передаются в сообщениях MP_CAPABLE в открытом виде, сообщения MP_JOIN защищаются с помощью HMAC-SHA256 ([RFC2104] с использованием алгоритма [RFC6234]) и переданных ключей, а также применяются стандартные проверки TCP для других сообщений (гарантия попадания порядковых номеров в окне [RFC5961]). Оставшиеся угрозы для MPTCP v0 отмечены в [RFC7430], а сохранившие влияние на протокол угрозы (т. е. изменение ADD_ADDR) включены в этот документ. Дополнительное рассмотрение вопросов безопасности приведено в разделе 5.

3. Обзор работы MPTCP

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

Все операции MPTCP указываются необязательными полями заголовка TCP. Для этого агентство IANA выделило протоколу MPTCP один номер опции TCP (Kind), как указано в разделе 7, а отдельные сигнальные сообщения указываются субтипами (Subtype), значения которых тоже хранятся в реестре IANA (раздел 7). Как во всех опциях TCP поле Length указывает размер в байтах с учетом 2 байтов полей Kind и Length.

В этом документе опции MPTCP всегда указываются символьными именами, такими как MP_CAPABLE, задающими опцию TCP с одним типом MPTCP и значением субтипа, определенным в разделе 7. Поле субтипа занимает 4 бита в начале поля данных (payload) опции, как показано на рисунке 3. Сообщения MPTCP описаны в последующих параграфах.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-----------------------+
|     Kind      |    Length     |Subtype|                       |
+---------------+---------------+-------+                       |
|                 Определяемые субтипом данные                  |
|                     (переменный размер)                       |
+---------------------------------------------------------------+

Рисунок 3. Формат опции MPTCP.


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

Однако остальные опции являются сигналами, которые не обязательно передавать в определенных пакетах, например, указание дополнительных адресов. Хотя у реализации MPTCP может быть желание передать опции как можно быстрее, объединение всех желаемых опций (MPTCP и обычного TCP, такие как селективные подтверждения SACK [RFC2018]) в одном пакете может оказаться невозможным. Поэтому реализация может выбрать отправку дубликатов ACK с дополнительными сигналами. Это меняет семантику дубликатов ACK, которые в обычном TCP как правило передаются лишь для сигнализации о потере сегмента [RFC5681]. Поэтому реализации MPTCP, получившей дубликат ACK с опцией MPTCP, недопустимо считать его сигналом перегрузки. Кроме того, реализации MPTCP не следует передавать более 2 дубликатов ACK подряд для отправки лишь опций MPTCP, чтобы предотвратить их ложную интерпретацию промежуточными устройствами как сигнала перегрузки.

Кроме того, стандартные проверки пригодности TCP (такие как контроль попадания в окно порядкового номера и номера подтверждения) должны выполняться до обработки сигналов MPTCP, как указано в [RFC5961], а начальные порядковые номера следует создавать в соответствии с рекомендациями [RFC6528].

3.1. Инициирование соединений

Инициирование соединения начинается с обмена SYN, SYN/ACK, ACK на одном пути. Каждый пакет включает опцию поддержки множества путей MP_CAPABLE (рисунок 4), которая говорит о поддержке отправителем Multipath TCP и желании использовать протокол в данном соединении.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-------+---------------+
|     Kind      |    Length     |Subtype|Version|A|B|C|D|E|F|G|H|
+---------------+---------------+-------+-------+---------------+
|                 Ключ отправителя опции (64 бита)              |
|                        (если Length > 4)                      |
+---------------------------------------------------------------+
|                 Ключ получателя опции (64 бита)               |
|                        (если Length > 12)                     |
+-------------------------------+-------------------------------+
|  Data-Level Length (16 битов) |  Checksum (16 битов, необяз.) |
+-------------------------------+-------------------------------+

Рисунок 4. Опция MP_CAPABLE.


Обмен MP_CAPABLE в этой спецификации (v1) отличается от заданного в версии v0. Если хост поддерживает несколько версий MPTCP, отправителю MP_CAPABLE следует указать наибольший поддерживаемый номер. В ответной опцииMP_CAPABLE получатель указывает желаемый для него номер версии, который должен быть не больше номера в исходной опции MP_CAPABLE. Однако имеется предостережение в части согласования версии со старыми реализациями, поддерживающими лишь v0. Узел, поддерживающий v0, ожидает увидеть в опции MP_CAPABLE сегмента SYN ключ инициатора. Однако, если инициатор уже перешел на v1, он не будет помещать ключ в сегмент SYN. В результате принимающая сторона будет игнорировать MP_CAPABLE в этом сегменте SYN и возвращать SYN/ACK без включения опции MP_CAPABLE. Инициатор может сразу вернуться к использованию TCP, а может попытаться организовать соединение MPTCP v0 (при поддержке этой версии), чтобы убедиться в поддержке получателем старой версии MPTCP. В общем случае соединение MPTCP v0 предпочтительней соединения TCP, однако в конкретном варианте развертывания может быть известно о малой вероятности поддержки другой стороной протокола MPTCP v0 и инициатор может отказаться от попытки создать соединение v0. Инициатор может кэшировать информацию о поддерживаемой партнером версии для ее использования в будущих соединениях.

Опция MP_CAPABLE имеет переменный размер и включает разные поля в зависимости от пакета, в который она помещается. Полностью опция MP_CAPABLE показана на рисунке 4.

Опция MP_CAPABLE передается в пакетах SYN, SYN/ACK и ACK, которые создают первый субпоток соединения MPTCP, а также в первом пакете с данными, если инициатор желает отправить их. Данные каждой опции указаны ниже (A инициатора, B — принимающая сторона).

  • SYN (A->B): только первые 4 октета (Length = 4).

  • SYN/ACK (B->A): ключ хоста B для этого соединения (Length = 12).

  • ACK (без данных) (A->B): ключ A, затем ключ B (Length = 20).

  • ACK (с данными) (A->B): ключ A, затем ключ B, Data-Level Length и необязательное поле Checksum (Length = 22 или 24).

Содержимое опции определяется флагами SYN и ACK в пакете, а также полем Length. На рисунке 4 отправителем и получателем названы отправитель и получатель пакета TCP (которые могут быть любым из хостов).

Начальный пакет SYN, содержащий лишь заголовок MP_CAPABLE, служит для указания запрашиваемой версии MPTCP и обмена флагами согласования свойств соединения, как описано ниже.

Эта опция служит для объявления 64-битовых ключей, которые конечные хосты создали для этого соединения MPTCP. Ключи служат для проверки подлинности добавляемых субпотоков данного соединения. Это единственный случай передачи ключа в линию в открытом виде (если не применяется Fast Close, параграф 3.5), все будущие субпотоки идентифицируются 32-битовым маркером (token), который является криптографическим хэш-значеним для ключа. Алгоритм хеширования зависит от выбранного способа аутентификации, а метод выбора описан ниже.

При получении начального сегмента SYN сервер с поддержкой стстояния генерирует случайный ключ и отвечает сегментом SYN/ACK. Метод создания ключа определяется реализацией. Ключ должен быть сложнопредсказуемым и должен быть уникальным для передающего хоста среди всех его действующих соединений MPTCP. Рекомендации по созданию случайных значений, применяемых в ключах, даны в [RFC4086]. Соединения индексируются на каждом хосте маркером (необратимый хэш ключа). Поэтому для реализации требуется сопоставление каждого маркера с соединением и, следовательно, с ключами для соединения.

Существует вероятность того, что хэш-значения двух ключей будут давать один маркер. Риск этот обычно невелик, если на хосте не используются десятки тысяч одновременных соединений. Поэтому реализации следует проверять список маркеров соединений, чтобы убедиться в отсутствии конфликтов, до отправки своего ключа, а при обнаружении конфликта следует генерировать новый ключ. Однако такая проверка будет дорогостоящей для серверов с тысячами соединений. Механизм согласования субпотоков (параграф 3.2) обеспечивает добавление субпотока лишь к нужному соединению (за счет криптографического согласования и проверки маркеров соединения в каждом направлении), а также попадание порядковых номеров в окно. Поэтому в худшем случае при возникновении конфликта маркеров новый субпоток не удастся добавить, но соединение MPTCP будет обеспечивать обычные услуги .

Поскольку генерация ключей зависит от реализации, не требуется, чтобы ключи были просто случайными числами. Реализация может выполнять обмен криптографическим материалом по отдельному каналу (out of band) и создавать из такого материала ключи для идентификации взаимодействующих элементов. Например, реализация может связать свои ключи MPTCP с ключами соединений TLS или SSH вышележащего уровня.

Если сервер не поддерживает состояний, он генерирует свои ключи проверяемым способом, что можно сделать с помощью хэширования квартета адресов и портов, порядкового номера и локального секрета (как это происходит для порядковых номеров TCP [RFC4987]). Таким образом, сервер сможет проверить, является ли он создателем возвращенного в следующей опции MP_CAPABLE ключа. Как и для сервера с состояними, следует проверять уникальность маркеров. Если маркеры не уникальны и нет возможности создать другой проверяемый ключ, соединение должно вернуться к использованию обычного TCP без отправки опции MP_CAPABLE в SYN/ACK.

Сегмент ACK передает ключи хостов A и B. Это первый случай появления ключа в линии, хотя предполагается, что хост A будет иметь у себя созданный ключ до отправки начального SYN. Возврат ключа B позволяет хосту B работать без поддержки состояния, как описано выше. Поэтому ключ A должен гарантированно доставляться хосту B, а для этого данный пакет должен передаваться с гарантией доставки.

Если у хоста B есть данные для первой передачи, гарантированная доставка ACK с MP_CAPABLE обеспечивается получением данных с опцией MPTCP DSS (параграф 3.3), содержащей DATA_ACK для MP_CAPABLE (первый октет в пространстве номеров данных). Однако, если хост A хочет отправить данные в начале, у него есть два варианта гарантированной доставки ACK с MP_CAPABLE. Если у хоста имеются данные для передачи сразу, первый сегмент ACK (с данными) будет включать также опцию MP_CAPABLE с параметрами данных (Data-Level Length и необязательное поле Checksum, как показано на рисунке 4). Если у хоста A нет данных для передачи сразу, он должен включить MP_CAPABLE в первый сегмент ACK, но без параметров данных. Если у A есть данные для передачи, он должен повторить опцию MP_CAPABLE из первого ACK с дополнительными параметрами данных. Эта опция MP_CAPABLE используется вместе с DSS и просто указывает (1) Data-Level Length для данных (payload) и (2) контрольную сумму (если ее использование согласовано). Эти минимальные сведения нужны для организации соединения MPTCP — они позволяют проверить данные (payload) и сучетом того, что это первые данные, становится известным исходный порядковый номер данных (Initial Data Sequence Number или IDSN), поскольку он создается на основе ключа, как указано выше. Перенос ключей в первом пакете данных позволяет механизмам гарантированной доставки TCP обеспечить надежную достаку пакета. Получатель будет подтверждать эти данные на уровне соединения с помощью Data ACK как при получении опции DSS.

Возможны ситуации, когда A и B одновременно попытаются передать начальные данные. Например, если у A в начале не будет данных для передачи, но ему нужно отправить данные до приема чего-либо от B, он будет использовать опцию MP_CAPABLE с параметрами данных (поскольку он не знает о наличии MP_CAPABLE в подтверждении ACK, которое еще не получено). В таком случае B тоже мог передать данные с опцией DSS, но они еще не приняты хостом A. В результате B имеет принятые данные с отображением MP_CAPABLE после того, как он передал данные с опцией DSS. Для корректной обработки таких ситуаций следует принять, что параметры данных в MP_CAPABLE семантически эквивалентны параметрам в опции DSS и могут использоваться взаимозаменяемо. Похожая ситуация может возникнуть при потере и повторной передаче MP_CAPABLE с данными. Кроме того, в случае выгрузки сегментации TCP опция MP_CAPABLE с параметрами данных может дублироваться в нескольких пакетах и реализация должна быть способна справиться с дубликатами отображений MP_CAPABLE, а также с дубликатами отображений DSS.

Дополнительно обмен MP_CAPABLE позволяет определить безопасное прохождение опций MPTCP в пакетах SYN. Если какая-либо из таких опций отбрасывается, MPTCP будет аккуратно возвращаться к обычному TCP, как указано в параграфе 3.7. Если любая из сторон согласования в любой момент сочтет согласование MPTCP скомпрометированным, например, промежуточное устройство повредит опции TCP или будут получены неожиданные номера ACK, хост должен остановить использование MPTCP и больше не включать опции MPTCP в последующие пакеты TCP. Другой хост тогда тоже вернется к обычному TCP с использованием механизма отката (fallback). Отметим, что новые субпотоки недопустимо создавать (с использованием процесса из параграфа 3.2), пока не была принята опция DSS, доставленная через путь (см. параграф 3.3).

Как и другие опции MPTCP, MP_CAPABLE начинается с полей Kind и Length, задающих тип и размер опции TCP. Первые 4 бита первого октета тела опции MP_CAPABLE (рисунок 4) определяют субтип опции MPTCP (раздел 7, для MP_CAPABLE эти биты имеют значение 0x0), а 4 оставшихся бита этого октета указывают используемую версию MPTCP (данной спецификации соответствует значение 1).

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

A

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

B

Второй бит (B) служит флагом расширяемости и в текщих реализациях должен иметь значение 0. Флаг будет применяться механизмами расширения в будущих спецификациях и его влияние будут определено позднее. Предполагается (но не обязательно), что этот флаг станет частью дополнительного механизма защиты, который не потребует смены номера версии протокола, но потребует заново определить некоторые элементы согласования. При получении сообщения с установленным флагом B, что непонятно, опция MP_CAPABLE в этом пакете SYN должна игнорироваться с возвратом соединения к обычному TCP. Предполагается, что отправитель повторит пакет, используя формат, совместимый с устаревшей спецификацией. Отметим, что размер опции MP_CAPABLE и назначение флагов D — H может меняться при установке B=1.

C

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

D — H

Оставшиеся биты D — H служат для согласования криптоалгоритма. В данной спецификации назначен лишь правый бит (H), указывающий применение HMAC-SHA256 (см. параграф 3.2). Реализация, поддерживающая лишь этот алгоритм должна устанавливать для бита H значение 1, а для битов D — G — значение 0.

Криптоалгоритм должен быть указан. Если флаги D — H установлены в 0, опция MP_CAPABLE должжна считаться некорректной и игнорироваться (т. е. нужно считать это переходом к обычному согрласованию TCP).

Выбор алгоритма аутентификации влияет также на алгоритм, применяемый для генерации маркера и IDSN. Данная спецификация задает лишь алгоритм SHA-256 (бит H), поэтому маркер должен принимать 32 старших бита значения SHA-256 [RFC6234] для ключа. Для IDSN должны выбираться 64 младших бита хэш-значения SHA-256 от ключа. Отметим, что ключ должен хэшироваться в сетевом порядке байтов. Младшими считаются правые биты результата SHA-256 в соответствии с [RFC6234]. В будущих спецификациях флаги могут позволить выбирать другие алгоритмы для генерации маркеров и IDSN.

Биты криптоалгоритма и контрольной суммы согласуют возможности похожим способом. Контрольная сумма (бит A) должна использоваться, если она нужна любому из хостов. Иными словами, для отказа от контрольной суммы оба хоста должны установить в своеих SYN бит A=0. решение подтверждается значением бита A в третьем пакете согласования (ACK). Например, если инициатор указал A=0 в пакете SYN, а отвечающий установил A=1 в SYN/ACK, контрольная сумма должна использоваться и инциатор установит A=1 в пакете ACK. Решение о применении контрольной суммы сохраняется реализацией в переменной состояния на уровне соединения. Если A=1 получено хостом, который не хочет использовать контрольную сумму, он должен вернуться к обычному TCP, игнорируя опцию MP_CAPABLE, как будто она недействительна.

При согласовании криптоалгоритма у отвечающей стороны есть выбор. Инициатор устанавливает 1 для каждого предлагаемого алгоритма (в данной версии это лишь бит H, который всегда имеет значение 1). Принимающая сторона устанавливает 1 лишь в одном бите (ее выбор). Основанием для такого поведения служит то, что отвечающей стороной обычно является сервер, который может иметь тысячи соединений и поэтому выберет простейший с точки зрения вычислительной сложности алгоритм (в зависимости от своей загрузки). Если отвечающая сторона не поддерживает (или не хочет применять) ни одного из предложенных алгоритмов, она должна вернуть отклик без опции MP_CAPABLE, переводя соединение на обычный протокол TCP.

Опция MP_CAPABLE применяется лишь в первом субпотоке соединения для его идентификации. Все остальные субпотоки применяют опцию MP_JOIN (параграф 3.2) для подключения к имеющемуся соединению.

Если SYN включает опцию MP_CAPABLE, но ее нет в SYN/ACK, предполагается, что отправитель SYN/ACK не поддерживает множество путей и сессия MPTCP должна перейти в обычную сессию TCP. Если в SYN нет опции MP_CAPABLE, ее включение в SYN/ACK недопустимо. Если третий пакет (ACK) не включает опцию MP_CAPABLE, сессия должна вернуться в обычный режим TCP. Это делается для совместимости с промежуточными устройствами на пути, которые отбрасывают часть или все опции TCP. Отметим, что реализация может пытаться повторить опции MPTCP несколько раз до принятия решения о возврате к обычному TCP (параграф 3.9).

Реакцию на отсутствие подтверждений для пакетов SYN определяет локальная политика. Предполагается, что отправитель в конечном итоге вернется к обычному TCP (без опции MP_CAPABLE) для работы через промежуточные устройства, которые могут отбрасывать пакеты с неизвестными опциями, однако число попыток использовать несколько путей задает локальная политика. В сети возможно изменение порядка пакетов SYN с MPTCP и без него, поэтому финальное решение определяется наличием или отсутствием опции MP_CAPABLE в третьем пакете согласования TCP. Если опции в нем нет, соединение следует вернуть к обычному TCP, как указано в параграфе 3.7.

Номер IDSN в соединении MPTCP генерируется из ключа, а алгоритм создания IDSN определяется согласованным алгоритмом аутентификации. Эта спецификация задает лишь алгоритм SHA-256, поэтому для IDSN хост должен брать 64 младших бита хэш-значения SHA-256 от ключа хоста, т. е. IDSN-A = Hash(Key-A), а IDSN-B = Hash(Key-B). Такая детерминированная генерация IDSN позволяет получателю обеспечить отсутствие пропусков порядковых номеров при старте соединения. SYN с опцией MP_CAPABLE занимает первый октет пространства номеров данных, хотя его не требуется подтверждать на уровне соединения до отправки первых данных (параграф 3.3).

3.2. Запуск нового субпотока

После запуска соединения MPTCP с помощью обмена MP_CAPABLE можно добавлять в него субпотоки. Хост знает свои адреса и может узнать адреса другого хоста в результате сигнального обмена, описанного в параграфе 3.4. Используя эту информацию, хост может инициировать новый поток черех адреса, которые еще не используются. Новые потоки может создавать любой из участвующих в соединении хостов, но предполагается, что обычно это делает инициатор исходного соедиения (эвристика представлена в параграфе 3.9).

Новый субпоток начинается с обычного обмена TCP SYN/ACK. Используется опция MPTCP MP_JOIN для указания соединения, к которому добавляется новый субпоток. В опции применяется ключевой материал, полученный при начальном согласовании MP_CAPABLE (параграф 3.1), которое задает также криптографический алгоритм для согласования MP_JOIN.

В этом разделе задано поведение MP_JOIN при использовании алгоритма HMAC-SHA256. Опция MP_JOIN включается в пакеты SYN, SYN/ACK и ACK трехэтапного согласования, но формат ее в каждом случае различается.

В первом пакете MP_JOIN (SYN), показанном на рисунке 5, инициатор передает маркер, случайное число и Address ID.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-----+-+---------------+
|     Kind      |  Length = 12  |Subtype|(rsv)|B|   Address ID  |
+---------------+---------------+-------+-----+-+---------------+
|                  Маркер получателя (32 бита)                  |
+---------------------------------------------------------------+
|            Случайное значение отправителя (32 бита)           |
+---------------------------------------------------------------+

Рисунок 5. Опция MP_JOIN (для начального SYN).


Маркер служит для указания соединения MPTCP и является криптографическим хэш значением от ключа получателя из начального согласования MP_CAPABLE (параграф 3.1). В этой спецификации маркеры для опции создаются алгоритмом SHA-256 [RFC6234], из результата которого берется 32 старших бита. Маркер, включенный в опцию MP_JOIN, является маркером, который получатель применяет для идентификации сообщения, т. е. хост A передает Token-B (создается из Key-B). отметим, что алгоритм генерации хэш-значения может быть переопределен выбором алгоритма криптографического согласования, как определено в параграфе 3.1.

MP_JOIN в SYN содержит не только маркер (не меняется в соединении), но и случайное значение (nonce), служащее для предотвращения replay-атак на метод аутентификации. Рекомендации по созданию таких случайных значений даны в [RFC4086].

Опция MP_JOIN включает поле Address ID, содержащее созданный отправителем опции идентификатор, указывающий адрес отправителя на случай замены адреса в заголовке IP промежуточным устройством. Численное значение этого поля создается отправителем и долно однозначно соответствовать IP-адресу передающего хоста. Поле Address ID позволяет удалять адреса (параграф 3.4.2) без необходимости знать, какой адрес отправителя используется на приемной стороне, что дает возможность работы через NAT с удалением адресов. Address ID также позволяет сопоставлять попытки организации новых субпотоков с сигнализацией адресов (параграф 3.4.1) для предотвращения дублирования субпотоков по одному пути, если MP_JOIN и ADD_ADDR переданы одновременно.

Address ID субпотока, используемые в начальном обмене SYN первого субпотока в соединении, являются неявными и имеют значение 0. Хост должен хранить отображение между Address ID и адресами для себя и удаленного партнера. Реализации также нужно знать, какие локальные и удаленные Address ID связаны с каждым из созданных субпотоков при удалении адресов на локальном или удаленном хосте.

Опция MP_JOIN в пакетах с флагом SYN включает также 4 бита флагов, 3 из которых являются резервными и должны быть сброшены (0) при передаче. Последний бит (B) указывает желание отправителя опции использовать этот субпоток (1) в качестве резервного path (B=1) или (2) сразу включить в соединение. Установкой B=1 отправитель опции запрашивает у удаленного хоста отправку данных через этот субпоток лишь в случае отсутствия доступных субпотоков с B=0. Политика для субпотоков подробно рассматривается в параграфе 3.3.8.

При получении SYN с опцией MP_JOIN, содержащей действительный маркер существующего соединения MPTCP приемной стороне следует ответить пакетом SYN/ACK с опцией MP_JOIN, содержащей случайное значение и усеченный (64 бита слева) код HMAC. Этот вариант опции показан на рисунке 6. Если маркер неизвестен или хост желает отвергнуть создание субпотока (например, в результате ограничения их числа), он будет возвращать сигнал сброса (RST), аналогичный применяемому для неизвестного порта в TCP, с опцией MP_TCPRST (параграф 3.6), содержащей код причины ошибки MPTCP. Хотя расчет HMAC требует криптографических операций, предполагается, что 32-битовый маркер в MP_JOIN SYN обеспечивает достаточную защиту от атак вслепую на истощение и не требуется механизма, позволяющего отвечающей стороне работать без поддержки состояния на этапе MP_JOIN.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-----+-+---------------+
|     Kind      |  Length = 16  |Subtype|(rsv)|B|   Address ID  |
+---------------+---------------+-------+-----+-+---------------+
|                                                               |
|             HMAC отправителя с отсечкой (64 бита)             |
+---------------------------------------------------------------+
|            Случайное значение отправителя (32 бита)           |
+---------------------------------------------------------------+

Рисунок 6. Опция MP_JOIN (для откликов SYN/ACK).


HMAC передают оба хоста — инициатор (A) в третьем пакете (the ACK), отвечающий (B) — во втором (SYN/ACK). Обмен HMAC на этом этапе позволяет хостам сначала обменяться случайными данными (в 2 первых пакетах SYN), которые служат «сообщением». Эта спецификация задает использование HMAC в соответствии с [RFC2104] вместе с алгоритмом хэширования SHA-256 [RFC6234] с выбором 160 битов слева (20 октетов). Ограниченное пространство потребовало отсечь HMAC в SYN/ACK до 64 битов (слева), но это приемлемо, поскольку применяются случайные значения. Таким образом, у атакующего есть единственный шанс угадать код HMAC, соответствующий переданному ранее партнером случайному значению (если код HMAC не корректен, соединение TCP закрывается, поэтому новое согласование MP_JOIN с новым случайным значением не требуется).

Аутентификационные данные инициатора передаются в его первом пакете ACK (третий пакет согласования), как показано на рисунке 7. Эти данные нужно передать с гарантией доставки, поскольку этот код HMAC передается лишь один раз. Поэтому прием такого должен вызывать отпраку обычного отклика TCP ACK и перадача пакета должна повторяться при отсутствии этого подтверждения ACK. Иными словами, отправка пакета ACK с MP_JOIN выполняется в субпотоке в состоянии PRE_ESTABLISHED и он переходит в состояние ESTABLISHED лиши в случае приема ACK от получателя. Передача данных в состоянии PRE_ESTABLISHED не допускается. Для резервных битов этой опции отправитель должен устанавливать значение 0.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-----------------------+
|     Kind      |  Length = 24  |Subtype|       (резерв)        |
+---------------+---------------+-------+-----------------------+
|                                                               |
|                                                               |
|           HMAC отправителя с отсечкой (160 битов)             |
|                                                               |
|                                                               |
+---------------------------------------------------------------+

Рисунок 7. Опция MP_JOIN (для первого ACK от инициатора)


Ключом для алгоритма HMAC в случае передачи первого сообщения хостом A будет Key-A, за которым следует Key-B, а в случае первого сообщения от хоста B — Key-B, за которым следует Key-A. Эти ключи были переданы с исходном согласовании MP_CAPABLE. «Сообщением» для алгоритма HMAC в каждом случае является конкатенация случайных значений каждого хоста (R) — для хоста A это будет R-A, за которым следует R-B, а для хоста B — R-B, за которым следует R-A.

Эти опции MPTCP совместно обеспечивают проверку подлинности при организации субпотока, как показано на рисунке 8.

         Хост A                                  Хост B
------------------------                       ----------
Адрес A1      Адрес A2                         Адрес B1
----------    ----------                       ----------
          |             |                                |
          |             |  SYN + MP_CAPABLE              |
          |--------------------------------------------->|
          |<---------------------------------------------|
          |          SYN/ACK + MP_CAPABLE(Key-B)         |
          |             |                                |
          |        ACK + MP_CAPABLE(Key-A, Key-B)        |
          |--------------------------------------------->|
          |             |                                |
          |             |   SYN + MP_JOIN(Token-B, R-A)  |
          |             |------------------------------->|
          |             |<-------------------------------|
          |             | SYN/ACK + MP_JOIN(HMAC-B, R-B) |
          |             |                                |
          |             |     ACK + MP_JOIN(HMAC-A)      |
          |             |------------------------------->|
          |             |<-------------------------------|
          |             |             ACK                |

      HMAC-A = HMAC(Key=(Key-A + Key-B), Msg=(R-A + R-B))
      HMAC-B = HMAC(Key=(Key-B + Key-A), Msg=(R-B + R-A))

Рисунок 8. Пример использования аутентификации MPTCP.


Если маркер, полученный хостом B, неизвестен или локальная политика не позволяет принять новый субпоток, получатель должен передать для этого субпотока TCP RST. Если это приемлемо, следует включить опцию MP_TCPRST с кодом причины Administratively prohibited (параграф 3.6).

Если маркер воспринят хостом B, но код HMAC, возвращенный хосту A, не соответствует ожидаемому, хост A должен закрыть субпоток пакетов TCP RST. В этом и последующих случаях при передаче RST, указанной в этом параграфе, отправителю следует передавать в RST опцию MP_TCPRST (параграф 3.6) с кодом причины MPTCP-specific error.

Если хост B не получил ожидаемого кода HMAC или опция MP_JOIN отсутствует в ACK, хост должен закрыть субпоток отправкой TCP RST.

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

Если принятый хостом A пакет SYN/ACK не включает опции MP_JOIN, хост A должен закрыть субпоток отправкой RST.

Выше приведены все случаи потери опции MP_JOIN. Если опция MP_JOIN вырезается из SYN на пути от A к B и хост B не прослушивает соответствующий порт, он будет отвечать обычным пакетом RST. Если в ответ на SYN с MP_JOIN получен пакет SYN/ACK без MP_JOIN (в результате вырезания на обратном пути или на выходном пути к хосту B, отвечающему как в обычной новой сессии TCP), субпоток не пригоден для использования и хост A должен закрыть субпоток отправкой RST.

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

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

3.3. Работа MPTCP и перенос данных

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

Data Sequence Mapping и Data ACK передаются в опции DSS (рисунок 9). Любое или оба значения могут передаваться в одном DSS в зависимости от флагов. Data Sequence Mapping задает отображение номеров в субпотоке на номера в соединении, а Data ACK подтверждает прием данных на уровне соединения. Эти функции подробно описаны в двух следующих параграфах.

Содержимое опции задает установка фланов, как показано ниже:

  • A — Data ACK присутствует;

  • a — Data ACK занимает 8 октетов (в противном случае 4);

  • M — DSN, SSN (Subflow Sequence Number — порядковый номер в субпотоке), Data-Level Length и Checksum (при согласовании) присутствуют;

  • m — DSN занимает 8 октетов (в противном случае 4).

Флаги a и m имеют значение лишь при установке соответствующих флагов A и M, а в ином случае они игнорируются. Максимальный размер опции при установке всех флагов составляет 28 октетов.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+----------------------+
|     Kind      |    Length     |Subtype| (reserved) |F|m|M|a|A|
+---------------+---------------+-------+----------------------+
|        Data ACK (4 или 8 октетов в зависимости от флагов)    |
+--------------------------------------------------------------+
|Data Sequence Number (4 или 8 октетов в зависимости от флагов)|
+--------------------------------------------------------------+
|              Subflow Sequence Number (4 октета)              |
+-------------------------------+------------------------------+
|  Data-Level Length (2 октета) |      Checksum (2 октета)     |
+-------------------------------+------------------------------+

Рисунок 9. Опция DSS (Data Sequence Signal).


Флаг F указывает завершение данных (Data FIN) и его установка означает, что отображение покрывает финальную часть данных от отправителя. Это эквивалент флага FIN в обычном TCP на уровне соединения. Соединение не закрывается, если не было обмена Data FIN, сообщения MP_FASTCLOSE (параграф 3.5) или определяемого реализацией тайм-аута передачи на уровне соединения. Назначение Data FIN и взаимодействие этого флага с флагами FIN на уровне субпотока, а также Data Sequence Mapping описаны в параграфе 3.3.3. Для остальных резервных битов реализации данной спецификации должны устанавливать значение 0.

Отметим, что контрольная сумма включается в эту опции лишь при условии ее согласования в обмене MP_CAPABLE (параграф 3.1). О наличии контрольной суммы можно судить по размеру опции. Если контрольная сумма указана, но ее использование не согласовано в MP_CAPABLE, получатель должен закрыть субпоток пакетом RST за несограсованное поведение. Если использование контрольной суммы согласовано, но ее нет, олучатель должен закрыть субпоток пакетом RST как поврежденный. В обоих случаях в RST следует включать опцию MP_TCPRST (параграф 3.6) с кодом причины MPTCP-specific error.

3.3.1. Отображение последовательности данных

Поток данных как целое можно собрать с помощью полей Data Sequence Mapping в опции DSS (рисунок 9), которые задают отображение порядковых номеров в субпотоке на номера в соединении. Это используется получателем для обеспечения упорядоченной доставки на прикладной уровень. Тогда как порядковые номера в субпотоке (обычные номера в заголовках TCP) имеют смысл лишь в данном субпотоке. Предполагается (но не требуется) использование SACK [RFC2018] в субпотоках для повышения эффективности.

Data Sequence Mapping определяет сопоставление пространства номеров субпотока с пространством соединения и задается начальными номерами в субпотоке и на уровне данных, а также размером области отображения. Явное отображение диапазонов данных, а не сигнализация на уровне пакетов выбрано для совместмости с ситуациями, когда сегментирование или объединение TCP/IP происходят вне стека, создающего поток данных (например, при выгрузке сегментрования TCP в сетевой адаптер или при работе через промежуточные прокси-ускорители PEP [RFC3135]). Это позволяет охватить одним отображением весь пакет и может быть полезно при передаче больших объемов данных.

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

Пространство номеров данных задано как абсолютное значение, а нумерация в субпотоках относительна (SYN при запуске субпотока имеет номер 0). Это сделано для устойчивости к промежуточным устройствам (таким как МСЭ), меняющим начальный порядковый номер (Initial Sequence Number или ISN) в субпотоках на случайное значение ISN.

Data Sequence Mapping включает также контрольную сумму охваченных отображением данных, если использование контрольных сумм согласовано в обмене MP_CAPABLE. Контрольные суммы позволяют обнаружить изменение данных (payload) в пути промежуточными устройствами, не знающими о MPTCP. Несовпадение контрольной суммы вызывает отказ субпотока или возврат к обычному TCP, как указано в параграфе 3.7, поскольку MPTCP уже не может точно знать пространство номеров субпотока у получателя для постороения Data Sequence Mapping. Без контрльной суммы данные могут быть доставлены приложению даже когда промежуточное устройство поменяло границы сегментов или собержимое и даже доставило не все сегменты, учтенные в Data Sequence Mapping. Поэтому рекомендуется использовать контрольную сумму, если достоверно не известно об отсутствии в пути таких устройств. Применяется стандартная контрольная сумма TCP [RFC0793] для данных, учтенных в отображении, и псевдозаголовка (рисунок 10).

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+--------------------------------------------------------------+
|                                                              |
|                Data Sequence Number (8 октетов)              |
+--------------------------------------------------------------+
|              Subflow Sequence Number (4 октета)              |
+-------------------------------+------------------------------+
|  Data-Level Length (2 октета) |        Нули (2 октета)       |
+-------------------------------+------------------------------+

Рисунок 10. Псевдозаголовок для контрольной суммы DSS.


Отметим, что порядковый номер данных в псевдозаголовке всегда имеет размер 64 бита, независимо от размера, используемого в самой опции DSS. Стандартный алгоритм контрольных сумм TCP был выбран потому, что он в любом случае применяется для субпотока TCP и если это происходит для данных до добавления псевдозаголовков, достаточно однократного расчет. Кроме того, аддитивность контрольной суммы TCP позволяет получить контрольную сумму для DSN_MAP путем простого сложения контрольных сумм для данных каждого включенного сегмента TCP и контрольной суммы псевдозаголовка DSS.

Контрольная сумма вычисляется TCP для непрерывной последовательности данных субпотока, поэтому недопустимо использовать в субпотоке TCP Urgent Pointer для прерывания имеющегося отображения. Однако при получении срочных данных в субпотоке их следует сопоставить с пространством номеров данных и доставить приложению, как это выполняется для Urgent-данных в обычном TCP.

Для предотвращения возможной взаимоблокироваки (deadlock) обработку на уровне субпотока следует выполнять независио от обработки на уровне соединения. Поэтому даже при отсутствии отображения номеров собпотока на номера данных следует подтверждать (ACK) доставку данных в субпотоке (если они попадают в окно). Однако эти данные не могут быть подтверждены на уровне соединения (параграф 3.3.2), поскольку понядковые номера данных неизвестны. Реализация может удерживать неотображенные данные в течение короткого времени в ожидании приема отображения для них. Неотображенные данные не могут быть считаться попавшими в окно приема на уровне соединения, поскольку это окно связано с порядковыми номерами данных (соединения) и при нехватке памяти у получателя данные будут отбрасываться. Если отображение для номеров из субпотока не поступило в окне приема соединения, субпоток следует считать поврежденным и закрыть пакетов RST, а неотображенные данные отбросить.

Порядковые номера данных всегда задаются 64-битовыми значениями и реализация должна поддерживать их в таком формате. Если соединение работает с малой скоростью и защита от переполнения (wrap) порядковых номеров не требуется, реализация может для оптимизации включать в Data Sequence Mapping и/или Data ACK лишь 32 младших бита порядкового номера, делая это независимо для каждого пакета. Реализация должна поддерживать прием и обработку 64- и 32-битовых значений порядковых номеров, но не требуется способность передавать оба.

Реализация должна передавать полный 64-битовый номер при работе с достаточно высокой скоростью, когда 32-битовые значения могут переполняться (wrap) в течение максимального срока жизни сегмента (Maximum Segment Lifetime или MSL) [RFC7323]. Размеры DSN, используемые в этих значениях (могут быть разными) объявляются с помощью флагов опции DSS. Реализации должны воспринимать 32-битовые DSN и неявно преобразовывать их в 64-битовые, инкрементируя старшие 32 бита при каждом достижении максимума в 32 младших (wrap). Должна быть реализована проверка того, что перенос (wrap, например, смена очень большого номера очень малым) произошел в ожидаемое время и не вызван пакетами с нарушенным порядком доставки.

Как и порядковые номера в обычном TCP, номера данных в соединении следует начинать не с 0, а с некого случайного значения, чтобы затруднить захват сессии вслепую. Эта спецификация требует у станавливать в IDSN каждого из хостов младшие 64 бита хэш-значения SHA-256 от ключа хоста, как описано в параграфе 3.1. Это нужно еще для того, чтобы получатель знал ожидаемый номер IDSN и мог проверить отсутствие начальных пакетов в соединении (это особо важно при одновременном начале передачи в двух субпотоках).

Отображение в Data Sequence Mapping должно применяться ко всему или части пространства порядковых номеров субпотока в содержащем опцию сегменте TCP. Его не требуется включать в каждый пакет MPTCP, поскольку пространство субпотока в пакете охватывается отображением, известным получателю. Это можно использовать для сокращения издержек в случаях, когда отображение известно заранее. Один из таких случаев возникает при наличии между хостами одного субпотока, а другой — при сборке сегментов данных в большие блоки (несколько пакетов).

Может применяться «бесконечное» отображение при возврате к обычному TCP пуетм сопоставления данных субпотока с данными соединения для продолжающегося соединения (параграф 3.7). Это достигается установкой в поле Data-Level Length опции DSS резервного значения 0. Для контрольной суммы в таком случае также устанавливается 0.

3.3.2. Подтверждение данных

Для обеспечения полной сквозной устойчивости к отказам MPTCP предоставляет подтверждения на уровне соединения, работающие как кумулятивные ACK для соединения в целом. Это делается с помощью поля Data ACK в опции DSS (рисунок 9). Поведение Data ACK аналогично кумулятивным ACK в стандартном TCP — они показывают количество успешно принятых данных (без пропусков). Для сравнения, ACK на уровне субпотока похожи на TCP SACK, с учетом возможно наличия пропусков в потоке данных на уровне соединения. Data ACK указывает следующий порядковый номер данных, которые приемная сторона ожидает получить. Data ACK, как и DSN, могут передаваться в 64-битовых значениях или в виде 32 младших битов. Если данные получены с 64-битовым DSN, они должны подтверждаться 64-битовым Data ACK. При получении DSN в 32 битах реализация может передавать 32- или 64-битовый Data ACK и приниматься должны оба варианта.

Data ACK подтвеждает, что данные и все требуемые сигналы MPTCP получены и восприняты удаленной стороной. Одной из важнейших функций Data ACK является указание левого края анонсированного окна приема. Как указано в параграфе 3.3.4, окно приема используют все субпотоки и оно привязано к Data ACK. В результате реализациям недопустимо использовать поле RCV.WND в сегменте TCP на уровне соединения, если это же значение не указано в опции DSS с полем Data ACK. Кроме того, раздельные подтверждения для соединения и субпотоков позволяют раздельную обработку и получатель может отбрасывать сегменты после подтверждения на уровне субпотока, например, в случае нехватки памяти при нарушении порядка доставкисегментов.

Отправителю MPTCP недопустимо освобождать данные из буфера передачи до их подтверждения в Data ACK, принятом через любой из субпоток, и на уровне передавшего эти данные субпотока. Первое условие обеспечивает живучесть соединений, а второе — живучесть и самосогласованность субпотока при необходимости повторной передачи данных. Отметим, однако, что при необходимости несколько раз повторить передачу данных через субпоток возникает риск блокировки окна передачи. В таком случае отправитель MPTCP может прервать субпоток с недопустимым поведением, передав в него RST с опцией MP_TCPRST (параграф 3.6), указывающей код ошибки.

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

3.3.3. Закрытие соединения

В обычном TCP пакет FIN сообщает получателю о завершении передачи данных отправителем. Что субпотоки могли работать назависимо и выглядеть в линии как TCP, FIN в MPTCP влияет лишь на передавший его субпоток. Это дает узлам свободу выбора используемых в каждый момент путей. Для FIN сохраняется семантика обычного TCP, т. е. субпоток закрывается после подтверждения (ACK) обеими сторона сегмента FIN от партнера.

Вызов приложением close() для сокета указывает отсутствие у него данных для передачи и в обычном TCP ведет к отправке FIN для соединения. В MPTCP имеется эквивалентный механизм DATA_FIN. Пакет DATA_FIN служит индикацией отсутствия у отправителя данных для дальнейшей передачи и поэтому может использоваться для проверки доставки всех отправленных данных. DATA_FIN, как FIN в обычном TCP является односторонним сигналом.

DATA_FIN указывается установкой (1) флага F в опции DSS (рисунок 9) DATA_FIN занимает 1 октет (последний) в пространстве номеров соединения. Отметим, что DATA_FIN учитывается в Data-Level Length, но не на уровне субпотока. Например, сегмент с DSN = 80, Data-Level Length = 11 и установленным флагом DATA_FIN будет отображать 10 октетов субпотока с номерами 80-89, а DATA_FIN будет иметь значение DSN = 90, поэтому данный сегмент, включающий DATA_FIN, будет подтвежден DATA_ACK = 91.

Отметим, что при передаче DATA_FIN в сегменте TCP без данных поле DSS должно содержать порядковый номер субпотока 0, Data-Level Length = 1 и порядковый номер данных, соответствующий самому. Поле контрольной суммы в таком случае учитывает лишь псевдозаголовок.

Семантика и поведение DATA_FIN такие же как у TCP FIN, но на уровне соединения. Отметим, что DATA_ACK передается лишь после успешной доставки всех данных на уровне соединения, поэтому сигнал DATA_FIN не связан с FIN в субпотоках. Допускается объединение этих сигналов в одном субпотоке, если в дугих субпотоках уже не остается данных. В противном случае может потребоваться повтрная передача данных в других субпотоках. По сути, хосту недопустимо закрывать действующик субпотоки, пока он не может сделать это безопасно, т. е. пока для всех остающихся данных не переданы DATA_ACK или сегмент с флагом DATA_FIN не будет единственным из оставшихся.

После подтверждения DATA_FIN все оставшиеся субпотоки должны быть закрыты стандартным обменом FIN. Обоим хостам следует передать FIN во все субпотоки, чтобы промежуточные устройства могли очистить свои состояния даже для субпотоков с отказами. Поощряется также снижение тайм-аутов (MSL) для субпотоков на конечных хостах после получения DATA_FIN. В частности, любые субпотоки, в очередях которых еще остаются данные (повторенные в другом субпотоке для подтверждения DATA_FIN) можно закрыть с помощью RST с опцией MP_TCPRST (параграф 3.6), указывающей причину too much outstanding data (слишком много ожидающих данных).

Соединение считается закрытым, когда DATA_FIN обоих хостов подтверждены DATA_ACK.

Как отмечено выше, стандартный пакет TCP FIN в отдельном субпотоке закрывает лишь данный субпоток. Если все субпотоки были закрыты с помощью FIN, но подтверждения DATA_FIN не получено, соединение MPTCP будет считаться закрытым лишь по тайм-ауту. Это предполагает, что реализации будут находиться в состоянии TIME_WAIT как на уровне субпотоков, так и для соединения (Приложение D). Это разрешает сценарии break-before-make, когда соединение теряется во всех субпотоках до того, как может быть организовано заново.

3.3.4. Получатель

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

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

Окно приема задается относительно DATA_ACK. Как и в TCP, получателю недопустимо сжимать окно справа (уменьшать DATA_ACK + окно приема). Получатель будет использовать порядковый номер для решения о восприятии пакета на уровне соединения.

При решении вопросов восприятия пакетов на уровне субпотока выполняется обычная проверка TCP на предмет попадания порядкового номера пакета в разрешенное окно приема. В MPTCP такая проверка выполняется лишь для окна на уровне соединения. Проверку следует выполнять на уровне субпотока, чтобы убедиться, что порядковые номера субпотока и отображения соответствуют условию SSN — SUBFLOW_ACK <= DSN — DATA_ACK, где SSN — номер принятого пакета в субпотоке, а SUBFLOW_ACK — RCV.NXT (следующий ожидаемый номер) для субпотока (эквивалентами на уровне соединения являются DSN и DATA_ACK). В обычном TCP попадающий в окно сегмент помещается в очередь упорядоченных или разупорядоченных пакетов. В Multipath TCP происходит то же самое, но на уровне соединения — сегмент помещается в очередь упорядоченных или разупорядоченных пакетов, если он попадает в окно на уровне субпотока и соединения. Стек все еще должен запоминать для каждого субпотока полученные сегменты, чтобы передавать ACK для них в субпотоке. Обычно это реализуется путем хранения разупорядоченных очередей (содержат лишь заголовки без данных) и запоминания кумулятивного ACK.

Для реализаций важно понимать приемлемый размер буфера приема. Нижней границей для полного использования сети будет максимальное произведение пропускной способности и задержки среди путей. Однако этого может оказаться недостаточно при потере пакетов в медленном субпотоке с их потором (параграф 3.3.6). Точной верхней границей будет максимальное время кругового обхода (RTT) среди путей, умноженное на общую пропускную способность путей. Это позволит все потокам работать с полной скоростью при ускоренном повторе передачи по пути с наибольшим RTT. Но даже этого может быть недостаточно для поддержки полной производительности при тайм-ауте повтора на пути с наибольшим RTT. Изучение связи стратегии повтора с размером буфера оставлено на будущее.

3.3.5. Отправитель

Отправитель запоминает анонсы окна приема от полуяателя. Ему следует обновлять свое окно приема лишь при увеличении максимально разрешенного порядкового номера (DATA_ACK + окно приема) в ответ на DATA_ACK. Это важно для возможности использования путей с разными RTT и, следовательно, разными контурами обратной связи.

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

Отправитель должен также помнить окна приема, анонсированные каждым субпотоком. Разрешенным окном для субпотока i будет (ack_i, ack_i + rcv_wnd_i), где ack_i — кумулятивный ACK в субпотоке i. Это предотвратит отправку данных промежуточному устройству, не готовому их принять.

Объединнение этих правил разрешает отправителю передавать сегменты данных с номерами на уровне соединения из диапазона (DATA_ACK, DATA_ACK + receive_window). Каждый из этих сегментов будет отображаться в субпотоки, пока порядковые номера субпотоков попадает в разрешенные для них окна. Отметим, что номера в субпотоках обычно не влияют на управление потоком данных, если для всех субпотоков анонсировано одинаковое окно приема. Эти номера будут влиять лишь на управление потокм данных в субпотоках с меньшим анонсированным окном приема.

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

3.3.6. Надежность и повтор передачи

Data Sequence Mapping позволяет отправителю повторить передачу данных с тем же номером в другом субпотоке. В таких случаях хост должен продолжать их передачу в исходном субпотоке для сохранения его целостности (промежуточные устройства могут повторять старые данные и/или отвергать пропуски в субпотоке), а получатель будет игнорировать дубликаты). Хотя это явно не оптимально, с учетом совместимости такое поведение приемлемо, а оптимизация может быть согласована в будущих версиях протокола. Однако следует отметить, что это свойство также позволяет отправителю всегда передавать одни данные с одним номером в разных субпотоках для надежности.

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

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

Отправитель должен хранить данные в своем буфере передачи, пока они не будут подтверждены (1) на уровне соединения и (2) на уровне передавшего субпотока. Благодаря этому, отправитель всегда может при возникновении необходимости повторить передачу в том же или ином субпотоке. Особым случаем является отказ субпотока — отправитель обычно повторяет отправку данных в другой доступный субпоток после тайм-аута, продолжая повторные попытки и в отказавшем субпотоке. Отправитель будет принимать отказ потока по достижении предопределенного числа повторов (это может быть меньше обычно для TCP предела MSL) или получении сообщения ICMP об ошибке и лишь тогда удалит ожидающие сегменты.

Если использовано множество попыток повтора, показавших плохую работу субпотока, это может привести к сбросу хостом этого субпотока с помощью RST. Однако нужны дополнительные исследования для понимания, когда и как сбрасывать плохо работающие субпотоки. Пакет RST для этой цели следует сопровождать опцией MP_TCPRST (параграф 3.6) с кодом ошибки Unacceptable performance.

3.3.7. Контроль перегрузок

Субпотоки в соединении MPTCP имеют разные окна насыщения. Для обеспечения беспристрастности в «пробках» и при объединении сетевых ресурсов нужно связать окна каждого субпотока, чтобы направить большую часть трафика в незагруженные каналы. Один из алгоритмов решение такой задачи представлен в [RFC6356], он не обеспечивает совершенного объединения ресурсов, но «безопасен» в том смысле, что его легко развернуть в современной сети Internet. В данном случае подразумевается, что на любом пути субпоток не занимает больше пропускной способности, чем занял бы отдельный поток по этому маршруту и алгоритм можно применять в узких местах сети, не мешая обычным потокам TCP.

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

3.3.8. Политика для субпотока

В рамках локальной реализации MPTCP хост может применять любую политику передачи трафика по множеству путей. В типичном случае, где нужно обеспечить максимальную пропускную способность, для передачи данных одновременно применяются все доступные пути с согласованным контролем перегрузок [RFC6356]. Однако возможны и иные подходы. Одним из вариантов является «все или ничего», когда второй путь держится в резерве на случай отказа первого, но возможен также вариант с «переполнением», когда второй путь подключается при перегрузке первого. Выбор скорей всего будет связан с расходами на использование канала, но может основываться и на таких свойствах, как задержка или ее вариации, если стабильность (задержек или полосы) важнее пропускной способности. Требования приложений подробно рассмотрены в [RFC6897].

Для эффективного выбора решения отправителю нужны полные сведения о «стоимости» пути, наличие которых маловероятно. Было бы желательно получать сведения о предпочтительных путях от приемной стороны, поскольку та зачастую является многодомной и может платить за расход полосы входящих потоков. Для решения этой задачи опция MP_JOIN (параграф 3.2) включает флаг B, позволяющий хосту указать своему партнеру, какой путь следует считать резервным и применять лишь при отказах других субпотоков (субпоток, где получатель указал B=1, не следует применять для передачи данных, пока имеются доступные субпотоки с B=0).

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

                     1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-----+-+
|     Kind      |     Length    |Subtype|(rsv)|B|
+---------------+---------------+-------+-----+-+

Рисунок 11. Опция MP_PRIO.


Другим вариантом применения MP_PRIO является установка флага B в субпотоке для прекращения его использования перед закрытием и удалением REMOVE_ADDR (параграф 3.4.2), например, для поддержки режима make-before-break, где добавляются новые субпотоки перед закрытием ранее применявшихся.

Следует отметить, что флаг B является запросом получателя данных лишь к их отправителю и тому следует прислушиваться к таким запросам. Однако хост не может предполагать, что отправитель выполнит запрос, поскольку локальная политика или технические ограничения могут переопределять запросы MP_PRIO. Отметим, что сигнал применяется в одном направлении и поэтому отправитель может продолжать использование субпотока для передачи данных, даже указав партнеру B=1.

3.4. Обмен информацией об адресах (поддержка путей)

Термин «управление путями» (path management) применяется для обмена информацией о дополнительных путях между хостами, которые в данном случае обеспечиваются наличием у хостов множества адресов. Дополнительная информация об архитектуре MPTCP приведена в [RFC6182].

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

Ниже перечислено несколько типичных операций протокола.

  • Соединение MPTCP исходно организовано между адресом и портом A1 хоста A и адресом и портом B1 хоста B. Если A является многодомным и многоадресным, он может начать новый субпоток с адреса A2 на B1, передав SYN с опцией MP_JOIN от A2 к B1, используя ранее объявленный B маркер для этого соединения. Если B является многодомным, он может попытаться организовать новый субпоток от B2 к A1, используя ранее объявленный маркер A. В обоих случаях SYN передается в порт, уже используемый принимающим хостом в этом соединении.

  • Одновременно (или после ожидания) передается опция ADD_ADDR (параграф 3.4.1) через имеющийся субпоток для информирования получателя о дополнительных адресах отправителя. Получатель может использовать эти сведения для организации нового субпотока по полученному адресу. В нашем примере A передает опцию ADD_ADDR, сообщающую B адрес и порт A2. Совместное использование опции в SYN и опции ADD_ADDR, включая тайм-ауты, определяется реализацией и может зависеть от локальной политики.

  • Если субпоток A2-B1 организован, хост B может использовать Address ID в опции MP_JOIN для сопоставления этого адреса с опцией ADD_ADDR, которая также прибудет в имеющемся субпотоке и B будет знать о наличии субпотока A2-B1, игнорируя ADD_ADDR. Если же B не получил A2-B1 MP_JOIN SYN, но принял ADD_ADDR, он может попытаться организовать новый субпоток с одного или нескольких своих адресов на адрес A2. Это позволяет создавать субпотоки, когда один из хостов расположен за NAT.

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

3.4.1. Анонсирование адресов

Опция ADD_ADDR в MPTCP анонсирует дополнительные адреса (возможно и порты), доступные на хосте (рисунок 12). Опцию можно использовать в любой момент действующего соединения в зависимости от желания отправителя разрешить множество путей и/или при появлении доступных путей. Как и для других опций MPTCP, отправитель должен выполнить стандартные проверки пригодности TCP, например, [RFC5961], до начала действия.

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

Address ID, полученные в MP_JOIN или ADD_ADDR, следует сохранять на приемной стороне в структуре данных, которая содержит все отображения Address ID на реальный адрес в соединении (указывается парой маркеров). Таким способом сохраняются сопостваления Address ID, наблюдаемого адреса отправителя и пары маркеров для будущей обработки данных управления для соединения. Отметим, что реализация может по своему усмотрению отбрасывать входящие анонсы адресов, например, для предотвращения обновлений состояния или по причине невозможности применить адрес (например, анонс адреса IPv6 хосту IPv4). Поэтому хост должен считать анонсы адресов «мягким» состоянием и может периодически обновлять их. Отметим, что реализация может кэшировать такие анонсы, даже если они сейчас не актуальны, но могут быть применены в будущем, например, адреса IPv4 при использовании IPv6 с ожиданием доступности IPv4 DHCP.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-------+---------------+
|     Kind      |     Length    |Subtype|(rsv)|E|  Address ID   |
+---------------+---------------+-------+-------+---------------+
|           Address (IPv4: 4 октата / IPv6: 16 октетов)         |
+-------------------------------+-------------------------------+
|   Port (2 октета, необязат.)  |                               |
+-------------------------------+                               |
|              Truncated HMAC (8 октетов, если E=0)             |
|                               +-------------------------------+
|                               |
+-------------------------------+

Рисунок 12. Опция ADD_ADDR.


Опция показана на рисунке 12 с указанием размера адресов IPv4. Для IPv6 размер адреса будет 16 октетов (вместо 4).

Два октета, указывающие используемый порт TCP, не обязательны и их наличие определяется по размеру опции. Хотя предполагается, что в ьошьшинстве случаев будет применяться тот же порт, который использован в начальном соединении (например, порт 80 сохранится для всех субпотоков, как и эфемерный порт у клиента), могут возникать ситуации (например, балансировка трафика по портам), где требуется явно указать другой порт. Если порт не задан, MPTCP следует пытаться пдключиться к указанному адресу через тот же порт, который уже используется субпотоком, где был передан сигнал ADD_ADDR (см. параграф 3.9).

Параметр Truncated HMAC в этой опции содержит 64 младших (справа) битов HMAC, согласованных и рассчитанных так же, как описано для MP_JOIN в параграфе 3.2. Для этой спецификации MPTCP задан лишь один алгоритм хэширования HMAC [RFC2104], использующий SHA-256 [RFC6234]. Как и в случае MP_JOIN, ключом для алгоритма HMAC при передаче сообщения от хоста Host A будет Key-A, за которым следует Key-B, а при передаче от хоста B — Key-B, затем Key-A. Это ключи, обмен которыми выполнен при начальном согласовании MP_CAPABLE. Сообщением для HMAC являются Address ID, IP-адрес и порт, которые предшествуют HMAC в опции ADD_ADDR. Если порт не указан в опции ADD_ADDR, в HMAC будут включаться два октета со значением 0. Назначение HMAC состоит в предотвращении вставки сигналов ADD_ADDR не уполномоченными элементами для перехвата соединения. Дополнительно HMAC предотвращает замену адреса в процессе передачи, если промежуточному элементу не известен ключ. Если хост получает опцию ADD_ADDR, для которой не удается проверить (подтвердить) HMAC, следует игнорировать эту опцию.

После субтипа (перед Address ID) указывается набор из 4 флагов. Данная спецификация задает лишь самый правый флаг E, остальные в настоящее время не используются и должны устанавливаться в 0 при передаче и игнорироваться при получении. Флаг E служит для обеспечения надежности этой опции. Поскольку опция часто передается в «чистых» сегментах ACK, ее доставка не гарантирована. Поэтому получатель свежей опции ADD_ADDR (где E=0) будет передавать ту же опцию назад, но без включения HMAC и с E=1 для индикации получения. В соответствии с локальной политикой отсутствие этого типа «эхо» может указывать исходному отправителю опции ADD_ADDR на необходимость ее повтора.

В связи с широким распространением NAT вполне вероятно, что хост может попытаться анонсировать не маршрутизируемые адреса [RFC1918]. Запрещать такое поведение нежелательно, поскольку возможны ситуации, когда оба хоста имеют дополнительные интерфейсы в одну частную (с немаршрутизируемыми адресами) сеть и хост может анонсировать такие адреса. Согласование MP_JOIN для создания нового субпотока (параграф 3.2) обеспечивает механизмы для минимизации угроз безопасности. Сообщение MP_JOIN содержит 32-битовый маркер, который однозначно указывает соединение принимающему хосту. Получив неизвестный маркер, хост будет отвечать пакетом RST. В редком случае, когда (чужой) маркер оказывается действительным на принимающем хосте, огранизация субпотока будет продолжена, но для проверки подлинности потребуется обмен HMAC. Этот обмен будет завершаться отказом, что обеспечивает достаточную защиту от ситуаций, когда 2 несоединенных хоста случайно создают новый субпоток по сигнализации частного адреса. Дополнительное рассмотрение вопросов безопасности, связанных с сообщениями ADD_ADDR которые, ошибочно или злонамеренно создают новые попытки MP_JOIN, рассмотрены в разделе 5.

Хосту, получившему ADD_ADDR, но потерпевшему неудачу при соединении с данным адресом IP и портом, не следует продолжать попытки подключения к этому адресу и порту в данном соединении. Отправитель, желающий инициировать новую попытку входящего соединения с ранее анонсированной парой адрес-порт, может обновить информацию ADD_ADDR, передав опцию снова.

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

Хост, желающий заменить Address ID, должен сначала удалить имеющийся идентификатор (параграф 3.4.2).

При нормальной работе MPTCP маловероятно наличие достаточного места в опциях TCP для включения ADD_ADDR вместе с опциями порядковых номеров данных (параграф 3.3.1). Поэтому предполагается отправка реализацией MPTCP опций ADD_ADDR в отдельных сегментах ACK. Однако, как было отмечено выше, реализации MPTCP недопустимо считать дубликаты ACK с любой опцией MPTCP, за исключением DSS, индикацией перегрузки [RFC5681] и реализации не следует передавать для сигнальных целей более 2 дубликатов ACK подряд.

3.4.2. Удаление адресов

Если в течение срока действия соединения MPTCP анонсированный ранее адрес стал недействительным (например, интерфейс отключили или IPv6 перестал быть предпочтительным), хосту, на которой это влияет, следует анонсировать такую ситуацию, чтобы партнер мог удалить субпотоки, связанные с этим адресом. Даже если адрес не используется в соединении MPTCP, но был ранее анонсирован, реализации следует анонсировать его удаление. Хост также может анонсировать, что действительный адрес IP больше не следует использовать, например, для выполнения процедуры make-before-break (сначала организовать, затем прерывать).

Отзыв адресов выполняется с помощью опции REMOVE_ADDR (рисунок 13), которая удаляет добавленный ранее адрес (список адресов) из соединения и прервывает субпотоки, использующие удаляемый адрес.

                     1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-------+---------------+
|     Kind      |Length = 3 + n |Subtype|(resvd)|   Address ID  | ...
+---------------+---------------+-------+-------+---------------+
                           (далее n-1 Address ID, если нужно)

Рисунок 13. Опция REMOVE_ADDR.


В целях безопасности получивший опцию REMOVE_ADDR хост должен убедиться в том, что затрагиваемый опцией путь (или пути) больше не используется, прежде чем инициировать закрытие. Получателю REMOVE_ADDR следует сначала инициировать отправку по этому пути TCP keepalive [RFC1122] и при получении отклика удалять путь не следует. Если обнаружится, что путь сохраняет активность, получившему опцию хосту следует прекратить использование указанного адреса для будущих соединений, но ответственность за разрыв субпотока ложится на хост, передавший REMOVE_ADDR. Перед удалением адреса запрашивающая сторона может также передать MP_PRIO (параграф 3.3.8) для запроса прекращения использования пути. Должны также выполняться обычные проверки TCP для субпотока (например, проверка корректности порядковых номеров и номеров ACK). Реализация может использовать отказы при таких проверках как часть обнаружения попыток вторжения или записывать событие в системный журнал.

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

Удаление адреса выполняется в соответствии с Address ID для учета наличия NAT и других промежуточных устройств, меняющих адрес отправителя. Если значение Address ID неизвестно, получатель будет игнорировать запрос.

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

3.5. Опция MP_FASTCLOSE

В обычном TCP имеются средства отправки сигнала RST для резкого закрытия соединения. В MPTCP обычный сигнал RST работает лишь на уровне субпотока, закрывая лишь его и не влияя на другие субпотоки. Соединение MPTCP остается активным на уровне данных, чтобы разрешить передачу обслуживания в режиме break-before-make (создать после прерывания). Поэтому требуется обеспечить «сброс» на уровне MPTCP, позволяющий резко закрыть соединение MPTCP целиком. Это делается с помщью опции MP_FASTCLOSE.

MP_FASTCLOSE служит для информирования партнера о закрытии соединения и прекращении восприятия данных. Причины использования MP_FASTCLOSE зависят от реализации. В обычном TCP не разрешается отправка RST, пока состояние соединения засинхронизировано [RFC0793]. Тем не менее, реализации позволяют отправлять RST в таком состоянии, например, при нехватке ресурсов операционной системы. В таких случаях MPTCP следует передавать MP_FASTCLOSE (рисунок 14).

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-----------------------+
|     Kind      |    Length     |Subtype|      (reserved)       |
+---------------+---------------+-------+-----------------------+
|                      Option Receiver's Key                    |
|                            (64 бита)                          |
+---------------------------------------------------------------+

Рисунок 14. Опция MP_FASTCLOSE.


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

Вариант A (ACK)

Хост A передает ACK с опцией MP_FASTCLOSE в субпоток, используя ключ хоста B, объявленный при начальном согласовании соединения. Во все остальные субпотоки хоста A передает обычный TCP RST для закрытия субпотоков. Затем хост A переходит в состояние FASTCLOSE_WAIT.

Вариант R (RST)

Хост A передает RST с обцией MP_FASTCLOSE во все субпотоки, используя ключ хоста B, объявленный при начальном согласовании соединения. Хост A может незамедлительно разорвать все субпотоки соединение.

Если хост A выбрал вариант A и передал ACK с опцией MP_FASTCLOSE, соединение проходит указанные ниже стадии.

  • При получении хостом B пакета ACK с опцией MP_FASTCLOSE, содержащей действительный ключ, этот хост (B) отвечает в том же субпотоке пакетом TCP RST и закрывает все субпотоки, передавая в них TCP RST. После этого хост B может закрыть соединение MPTCP целиком (с прямым переходом в состояние CLOSED).

  • Как только хост A получит TCP RST для остающихся субпотоков, он будет закрывать эти субпотоки и разрывать соединение целиком (переход из состояния FASTCLOSE_WAIT в CLOSED). Если хост A вместо TCP RST получает MP_FASTCLOSE , это говорит об одновременной попытке обеих сторон закрыть соединение. Хосту A следует ответить сигналом TCP RST и разорвать соединение.

  • Если хост A не получает TCP RST в отклике на свою опцию MP_FASTCLOSE в течение тайм-аута повтора RTO (для субпотока, где была передана опция MP_FASTCLOSE), ему следует повторить MP_FASTCLOSE. Чтобы предотвратить слишком долгое удержание соединения, число повторов передачи следует ограничить (предел зависит от реализации), рекомендуется значение 3. Если хост A не получил в ответ TCP RST, ему следует передать TCP RST с опцией MP_FASTCLOSE, когда он освобождает состояние, чтобы очистить состояния на промежуточных устройствах.

Если хост A решит закрыть соединение с помощью варианта R и передаст RST с опцией MP_FASTCLOSE, хост B при получении RST с опцией MP_FASTCLOSE, содержащей действительный ключ, разрывает все субпотоки путем передачи TCP RST. После этого хост B может закрыть соединение MPTCP цкликом (переходит в состояние CLOSED).

3.6. Сброс субпотока

Реализации MPTCP может также потребоваться передать обычный TCP RST для форсированного закрытия субпотока. Хост передает TCP RST, чтобы закрыть субпоток или отвергнуть попытку создания субпотока (MP_JOIN). Чтобы принимающий хост мог значть причину закрытия или отклонения субпотока, в пакет TCP RST можно включить опцию MP_TCPRST (рисунок 15). Хост может использовать эту информацию для принятия решения, например, о повторении попытки снова создать субпоток сразу или позднее, а также отказе от нее.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-----------------------+
|     Kind      |    Length     |Subtype|U|V|W|T|    Reason     |
+---------------+---------------+-------+-----------------------+

Рисунок 15. Опция MP_TCPRST.


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

Флаг T используется отправителем для указания временного (Transient, T = 1) или постоянного (Permanent, T = 0) состояния ошибки. Если ошибка считается временной отправителем сегмента RST, получатель этого сегмента может попытаться создать субпоток для этого соединения снова, используя тот же путь. Время, которое следует выждать перед повтором, зависит от реализации, но следует учитывать свойства отказа, заданные представленным кодом причины. Если ошибка считается постоянной, получателю сегмента RST не следует повторять попытки организации субпотока для данного соединения по этому пути. Флаги U, V и W эта спецификация не задает, резервируя их на будущее. Реализации этого документа должны сбрасывать (0) эти флаги, а получатели должны игнорировать их.

8-битовое поле Reason указывает код причины прерывания субпотока.

Неуказанная ошибка (0x00)

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

Ошибка, связанная с MPTCP (0x01)

Ошибка обнаружена при обработке опций MPTCP. Это обычный код в случае отправки RST для закрытия субпотока по причине недействительного отклика.

Нехватка ресурсов (0x02)

Этот код говорит о нехватке у передавшего его хоста ресурсов для поддержки прерываемого субпотока.

Административный запрет (0x03)

Запрошенный поток запрещен правилами передавшего код хоста.

Слишком много ожидающих данных (0x04)

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

Неприемлемая производительность (0x05)

Производительность субпотока слишком мала по сравнению с другими субпотоками соединения Multipath TCP.

Влияние промежуточного устройства (0x06)

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

3.7. Возврат к TCP

Иногда на пути могут присутствовать промежуточные устройства, препятствующие работе MPTCP. Пртокол MPTCP разрабатывался с учетом разных промежуточных устройств (раздел 6), однако сохраняется возможность отказа отдельных субпотоков работать в рамках требований MPTCP. К таким случаям относится потеря опций MPTCP и изменение содерживаго пакетов пути. При возникновении таких событий требуется «откат» к обычной, безопасной работе, что можно сделать путем возврата к оычному протоколу TCP или удаления проблемного субпотока.

При старте соединения MPTCP (т. е. в первом субпотоке) важно обеспечить полную совместимость пути с MPTCP и передачу каждому хосту всех требуемых опций MPTCP. При описанном в параграфе 3.1 согласовании следует возвращаться к обычному TCP, если любое из сообщений SYN не включает опций MPTCP. Так же следует (и желательно) поступать, если хост не поддерживает MPTCP или путь не позволяет передать опции MPTCP. При попытке присоединения к имеющемуся соединению MPTCP (параграф 3.2) следует закрывать субпоток с помощью опции MP_JOIN, если путь пне поддерживает MPTCP и опции MPTCP не приходят в SYN.

Однако следует рассмотреть еще ожин важный случай, когда опции MPTCP приходят в пакетах SYN, но не приходят в обычных сегментах. Если субпоток является первым в соединении и находящиеся в процессе передачи данные являются смежными (непрерывными), проблему можно решить с помощью приведенных ниже правил.

  • Отправитель должен включать опцию DSS с Data Sequence Mapping в каждый сегмент, пока один из переданных сегментов не будет подтвержден с опцией DSS, содержащей Data ACK. При получении такого подтверждения отправитель будет знать о прохождении опции DSS в обоих направлениях и сможет передавать эту опцию реже (не в каждом сегменте).

  • Однако при получении ACK (не просто для SYN) без опции DSS с Data ACK, отправитель узнает об отсутствии поддержки MPTCP на пути. Если это происходит в дополнительном субпотоке (т. е. начатом с опцией MP_JOIN), хост должен закрыть поток с помощью пакета RST, в который следует включить опцию MP_TCPRST (параграф 3.6) с кодом причины «Middlebox interference».

  • В случае получения ACK в первом потоке (открытом с опцией MP_CAPABLE) до создания дополнительных субпотоков, реализация должна отказаться от MPTCP и рернуться к протоколу TCP. Отправитель передает одно финальное отображение Data Sequence Mapping с нулевым значением Data-Level Length, указывающим бесконечное отображение (для информирования другой стороны в случае отбрасывания опций лишь в одном направлении), а затем возвращается к передаче данных в одном субпотоке без опций MPTCP.

  • Если субпоток прерывается в процессе работы, например, при смене маршрутизации с переходом на путь, не поддерживающий опции MPTCP, после обнаружения этого (по заполнению буфера передачи на уровне субпотока или по отсутствию отображений для отправки DATA_ACK), субпоток следует считать оборванным и закрывать с помощью RST, поскольку данные невозможно доставить на уровень приложения и нет возможности надежно передать сигнала возврата к TCP. В пакет RST следует включать опцию MP_TCPRST (параграф 3.6) с кодом причины «Middlebox interference».

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

До сих пор рассматривалась потеря опций MPTCP в начале или в процессе работы соединения. Как указано в параграфе 3.3, каждая часть данных, для которой имеется отображение, охватывается контрольной суммой, если применение контрольных сумм согласовано. Этот механизм служит для обнаружения изменений, которые промежуточные устройства могли внести в данные (добавление, удаление, изменение). При любом изменении данных в пути контрольная сумма не совпадет. Применение контрольных сумм позволяет также обнаружить изменение размера данных в субпотоке, которое делает отображение Data Sequence Mapping недействительным. Отправитель больше не знает, с каким порядковым номером в субпотоке будет работать получатель (промежуточные устройства будут передавать фиктивные ACK) и не может указывать новые отображения. Кроме допустимого на программном уровне изменения данных возможны изменения при пересечении границ сегмента MPTCP, повреждающие данные. Поэтому никаким данным от начала сегмента, в котором не совпала контрольная сумма, доверять нельзя.

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

При использовании множества субпотоков находящиеся в процессе передачи субпотоком данные могут не быть непрерывной частью общего потока, распределеяемого по разным субпотокам. Из-за упомянутых выше проблем невозможно определить внесенные в данные изменения (в частности, изменения нумерации в субпотоках). Поэтому невозможно восстановить субпоток и затронутые изменениями потоки нужно немедленно закрывать с помощью RST, включающего опцию MP_FAIL (рисунок 16), которая указывает порядковый номер в начале сегмента (определяется по Data Sequence Mapping) с несовпадением контрольной суммы. Отметим, что опция MP_FAIL требует использования полного 64-битового номера даже в случаях передачи 32-битовых номеров в сигналах DSS для пути.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+----------------------+
|     Kind      |   Length=12   |Subtype|      (reserved)      |
+---------------+---------------+-------+----------------------+
|                                                              |
|                 Data Sequence Number (8 октетов)             |
+--------------------------------------------------------------+

Рисунок 16. Опция MP_FAIL.


Получатель опции должен отбросить все данные после указанного номера и недопустима отправка для них DATA_ACK (параграф 3.3.6).

Особым случаем является отказ при проверке контрольной суммы в единственном субпотоке. Если известно, что неподтвержденные данные, которые еще в сети, непрерывны (обычно это врено в случае с одним субпотоком), можно применить бесконечное отображение к потоку без необходимости сначала закрыть его, по существу отключая все последующие сигналы MPTCP. В этом случае при обнаружении получателем ошибки в контрольной сумме при единственном субпотоке, он будет возвращать опцию MP_FAIL в пакете ACK на уровне субпотока, указывая порядковый номер данных (не субпотока) в начале сегмента, где найдена ошибка. Отправитель получит эту информацию и при непрерывности неподтвержденных данных будет сообщать о бесконечном отображении. Это отображение будет опцией DSS (параграф 3.3) для первого нового пакета, содержащей отображение Data Sequence Mapping, указанное задним числом и задающее начало порядковых номеров субпотока в наиболее свежем сегменте, который был (как известно) доставлен без повреждений (т. е. для него получено DATA_ACK). С этого момента изменения данных промежуточным устройством не будут влиять на MPTCP, поскольку поток данных эквивалентен обычно сессии TCP. Хотя в теории путь может быть поврежден лишь в одном направлении (и сигнал MP_FAIL будет влиять токо на одно направление), для упрощения реализации получатель MP_FAIL все равно должен отвечать опцией MP_FAIL в обратном направлении и полностью возвращаться к обычной сессии TCP.

В редких случаях, когда данные не являются непрерывными (применяется лишь один субпоток, но в нем повторно передаются данные из недавно неаккуратно закрытого субпотока), получатель должен закрыть субпоток сигналом RST с опцией MP_FAIL. Получатель опции должен отбросить все данные после указанного порядкового номера. Отправитель может попытаться создать новый субпоток, относящийся к тому же соединению и в этом случае ему следует незамедлительно перевести этот субпоток в режим с одним путем, установив бесконечное отображение Data Sequence Mapping. Это отображение будет начинаться с порядкового номера данных из опции MP_FAIL.

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

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

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

3.8. Обработка ошибок

В дополнение к описанному выше механизму «отката» может потребоваться обработка стандартных ошибок TCP специфическими для MPTCP сопособами. Изменение семантики, например, релевантность RST, рассматривается в разделе 4. По возможности сохраняется обычное для TCP поведение. Ниже приведен список ошибок и соответствующее поведение MPTCP.

  • При неизвестном маркере в опции MP_JOIN (а также отказе при проверке HMAC для опции MP_JOIN в ACK или отсутствии MP_JOIN в отклике SYN/ACK) передается RST (аналогично поведению TCP для неизвестного порта).

  • При выходе DSN за пределы окна (при нормальной работе) данные отбрасываются без передачи Data ACK.

  • Запросы на удаление неизвестных Address ID просто игнорируются.

3.9. Эвристика

Имеется много эвристических подходов, которые нужны для повышения производительности и развертывания, но не требуются для корректности протокола. Такие вопросы рассматриваются в этом параграфе. Обсуждение буферизации и поведения окна приема приведено в параграфах 3.3.4 и 3.3.5, а повтороной передачи — в параграфе 3.3.6.

3.9.1. Использование портов

При типичных вариантах работы реализации MPTCP следует использовать порты, которые уже применяются. Иными словами, портом назначения в SYN с опцией MP_JOIN следует указывать тот же удаленный порт, который был задан для первого субпотока в соединении. Локальному порту для таких SYN следует совпадать с локальным портом для первого субпотока (поэтому реализации следует резервировать эфемерные порты для всех локальных адресов IP), хотя в некоторых случаях это невозможно. Эта стратегия предназначена для обеспечения максимальной вероятности прохождения пакетов SYN через МСЭ или NAT у получателя и предотвращения путаницы для программ сеетвого мониторинга.

Однако во многих случаях хост пожелает указать конкретный порт, который следует использовать. Эта возможность обеспечивается опцией ADD_ADDR, описанной в параграфе 3.4.1. Таким образом, можно использовать множество субпотоков между парой адресов с балансировкой нагрузки через разные порты (например, с некоторыми реализациями ECMP [RFC2992]).

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

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

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

Если у хоста есть данные, буферизованные для партнера (это подразумевает, что приложение получило запрос на такие данные), хост создает 1 субпоток для каждого начального окна буферизованных данных. При этом следует учитывать ограничение скорости добавления новых субпотоков, а также ограничение числа потоков для одного соединения. Хост может менять эти значения на основе свединий о трафике и характеристиках пути. Отметим, что такой эвристики может оказаться недостаточно. Трафик многих распространенных приложений (например, загрузка файлов) является асимметричным и многодомный хост может оказаться клиентом, который никогда не заполнит свои буферы и на основании такой эвристики просто не будет применять MPTCP. Здесь помогут расширенные API, позволяющие приложениям указать требования к трафику, которые помогут принять решение.

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

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

Здесь рассмотрены некоторые факторы, которые следует учитывать в эвристике MPTCP, но это не обязатель.

3.9.3. Обработка отказов

Требования к обработке неожиданных сигналов в MPTCP приведены в параграфе 3.8. Однако имеются случаи отказов, когда хосту приходится выбирать поведение. Например, в параграфе 3.1 указано, что хосту следует попытаться вернуться к обычным TCP SYN после одного или нескольких отказов MPTCP SYN в соединении. Хост может хранить системный кэш для такой информации, чтобы отказаться от MPTCP для определенного целевого хоста и даже для интерфейса, при повторяющихся отказах MPTCP. Продолжительность кэширования будет зависеть от реализации.

Другой случай может возникать при отказе согласования MP_JOIN. В параграфе 3.8 сказано, что некорректное согласование должно приводить к закрытию субпотока командой RST. Хост с активной системой предотвращения вторжений может заблокировать пакеты MP_JOIN от источника при возникновении множественных отказов MP_JOIN. С точки зрения инициатора при отказе MP_JOIN не следует пытаться организовать соединение с тем же адресом IP и портом в течение срока едйствия соединения, если другой хост не обновит информацию с помощью опции ADD_ADDR. Отметим, что опция ADD_ADDR является лишь информационной и не гарантирует попытки соединения от другого хоста.

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

4. Вопросы семантики

Для поддержки работы по нескольким путям семантика некоторых понятий TCP была изменена (см. ниже).

Sequence number — порядковый номер

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

ACK — подтверждение

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

Duplicate ACK — дубликат подтверждения

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

Receive Window — окно приема

Окно приема в заголовке TCP указывает размер свободного пространтсва в буфере на уровне соединения (в отличие от пространства субпотока) на приемной стороне. Семантика похожа на обычный TCP, но окно приема должно интерпретироваться передающей стороной относительно порядкового номера в DATA_ACK, а не в заголовке ACK субпотока. Это сохраняет привычный контроль перегрузок. Отметим, что некоторые промежуточные устройства могут менять окно приема, поэтому хосту следует использовать максимальное из недавно наблюдавшихся в субпотоках значение, а также поддерживать окна на уровне субпотоков для их обработки.

FIN — завершение

Флаг FIN в заголовке TCP применяется лишь к субпотоку, где он был передан, а не к соединению в целом. На уровне соединения роль FIN играет опция DATA_FIN.

RST — сброс

Флаг RST в заголовке TCP применяется лишь к субпотоку, где он был передан, а не к соединению в целом. Опция MP_FASTCLOSE обеспечивает функциональность Fast Close (RST) на уровне соединения MPTCP.

Address List — список адресов

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

5-tuple — квинтет

Квинтет (5-tuple) из протокола и пар локального и удаленного адресов и портов, представляемый API ядра уровню приложения без поддержки множества путей, который применяется в первом субпотоке, даже если позднее субпоток был закрыт и удален из соединения. Этот и другие связанные API вопросы рассматриваются в [RFC6897].

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

Как указано в [RFC6181], добавление поддержки нескольких путей в TCP создает множество новых классов угроз. Для их предотвращения в [RFC6182] представлен набор требований к защитным решениям для MPTCP. Основная цель защиты MPTCP состоит в том, чтобы ьыть «не хуже» обычного TCP. Основные требования защиты указаны ниже.

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

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

  • Защита от повторного использования, т. е. контроль «свежести» запросов на удаление и добавление.

Для решеения этих задач в MPTCP служит алгоритм согласования с хешированием, описанный в параграфах 3.1 и 3.2.

защита соединения MPTCP зависит от ключей, совместно применяемых й раз при старте первого субпотока и больше не передаваемых через сеть (если не применяется механизм Fast Close, параграф 3.5). Для упрощения демультиплексирования и предотвращения передачи криптографического материала в новых субпотоках применяется усеченный хэш этих ключей как идентификатор соединения (token). Конкатенация ключей служит ключом для создания кодов HMAC, используемых при создании субпотоков, для проверки подлинности сторон и возможности получения партнером трафика, переданного по новому адресу. Replay-атаки все еще возможны, если применяются лишь ключи, поэтому в согласовании используются одноразовые случайные числа (nonce) на обеих сторонах — это обеспечивает разные значения HMAC в каждом согласовании. Рекомендации по генерации случайных чисел для ключей приведены в [RFC4086] и рассмотрены в параграфе 3.1. Значения nonce действительны в течение попытки организовать соединение TCP. Коды HMAC служат также для защиты опции ADD_ADDR от угроз, указанных в [RFC7430].

Использование битов возможности шифрования в начальном согласовании соединения для задания конкретного алгоритма позволяет в будущем разверныть дополнительные алгоритмы шифрования. Тем не менее, это согласование уязвимо для активных атак с понижением уровня защиты, если злоумышленник может менять биты возможностей шифрования в откликах от получателя для выбора более слабого алгоритма. Представленный в этом документе механизм обеспечивает защиту от всех лавинных атак и перехватов, рассмотренных в [RFC6181].

Предложенный в параграфе 3.1 вариант согласования версии при использовании разными версиями MPTCP общего формата позволяет расположенному на пути злоумышленнику возможность теоретической атаки для снижения версии. Поскольку протоколы v1 и v0 используют разное согласование, такая атака потребует от клиента повтороной организации соединения с версией v0 и поддержки этой версии сервером. Отметим, что атакующий в пути будет иметь доступ к необработанным данным, обходящие все другие механизмы защиты на уровне TCP. Как отмечено в Приложении E, этот документ задает удаление поля AddrID [RFC6824] из опции MP_PRIO (параграф 3.3.8), что исключает возможность теоретической атаки, когда злоумышленник может перевести субпоток в состояние резервного.

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

В параграфе 3.4.1 отмечено, что хост может анонсировать свои приватные (не маршрутизируемые) адреса, но они могут относится также к другим хостам сети получателя. Согласование (параграф 3.2) предотвращает организацию субпотоков с некорректными хостами, однако такие анонсы могут вызывать нежелательный трафик согласований TCP. Это свойство MPTCP может стать целью DoS-эксплойтов, когда злонамеренный участник соединения MPTCP побуждает получателя соединяться с другими хостами сети. Поэтому реализациям следует рассмотреть эвристические подходы (параграф 3.9) для снижения таких влияний на стороне клиента и сервера.

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

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

Хотя эта спецификация определяет «промежуточное» решение по защите, отвечающее критериям, указанным в начале этого параграфа, и анализу угроз в [RFC6181], усложнение атак очевидно и будущей версии MPTCP наверняка потребуется более строгая защита. Имеется несколько способов улучшения защиты MPTCP, часть которых совместима с описанным в этом документе протоколом MPTCP, другие могут оказаться несовместимыми. На данный момент лучшим решением является обретение опыта работы с текущим подходом и проверка точности анализа угроз.

Возможные способы повышения уровня безопасности MPTCP перечислены ниже.

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

  • Задание способа защищенной передачи данных в MPTCP без изменения сигнальной части протокола.

  • Определение защиты, требующей дополнительного пространства опций, возможно в сочетании с предложением «длинных опций» для расширения пространства опций TCP (например, [TCPLO]) или на основе текущего подхода со вторым этапом защиты на основе опций MPTCP.

  • Пересмотр решения рабочей группы об использовании для сигнализации MPTCP лишь опций TCP и поиск возможности сигнализации также в данных TCP (payload).

При разработке MPTCP предусмотрено несколько методов идентификации новых механизмов защиты, включая:

  • доступные флаги MP_CAPABLE (рисунок 4);

  • доступные субтипы в опции MPTCP (рисунок 3);

  • поле Version в MP_CAPABLE (рисунок 4).

6. Взаимодействие с промежуточными устройствами

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

Основной проблемой является использование новой опции TCP. ПУ следует пересылать пакеты с неизвестными опциями без их изменения, но это делают не все. Предполагается наличие ПУ, вырезающих опции и передающих данные, копирующих одну опцию в разные сегменты (например, при сегментации) или отбрасывающих опции при слиянии сегментов. MPTCP использует одну новую опцию TCP (Kind) и все сообщения указываются значениями субтипа (раздел 7). Это снижает вероятность передачи лишь некоторых опций, используемых MPTCP, и ключевыми вопросами становятся различие путей и присутствие флага SYN.

Пакеты MPTCP SYN в первом субпотоке соединения включают опцию MP_CAPABLE (параграф 3.1). При ее отбрасывании MPTCP следует возвращаться к обычному TCP. Если отбрасываются пакеты с опцией MP_JOIN (параграф 3.2), путь просто не используется.

Если ПУ вырезает опции, не меняя остального, для MPTCP это безопасно. Если опция MP_CAPABLE отброшена на прямом или обратном пути, хост-инициатор просто вернется к TCP, как описано в параграфе 3.1 и показано на рисунке 17.

  Хост A                                Хост B
    |              Middlebox M            |
    |                   |                 |
    | SYN (MP_CAPABLE)  |        SYN      |
    |-------------------|---------------->|
    |                SYN/ACK              |
    |<------------------------------------|
a) Опция MP_CAPABLE вырезается на прямом пути

  Хост A                                  Хост B
    |           SYN (MP_CAPABLE)            |
    |-------------------------------------->|
    |             Middlebox M               |
    |                  |                    |
    |    SYN/ACK       |SYN/ACK (MP_CAPABLE)|
    |<-----------------|--------------------|
b) Опция MP_CAPABLE вырезается на обратном пути

Рисунок 17. Соединение с вырезанием опции.


Пакеты SYN в субпотоках включают опцию MP_JOIN. Если эта опция вырезается на обратном пути, пакет с точки зрения хоста B становится обычным SYN. В зависимости от наличия на указанном порту прослушивающего сокета хост B будет отвечать пакетом SYN/ACK или RST (отказ в соединении для субпотока). Когда хоста A получить SYN/ACK, он ответит пакетом RST по причине отсутствия в SYN/ACK опции MP_JOIN и маркера. В любом случае организация субпотока завершается отказом и не влияет на соединение MPTCP в целом.

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

Однако опции могут вырезаться из пакетов данных, как отмечено в параграфе 3.7. При частично вырезании опций MPTCP поведение протокола становится неопределенным. Если теряются некоторые Data Sequence Mapping, соединение будет работать, пока существует сопоставление с данными субпотоков (например, если передано несколько сопоставлений). Если часть пространства номеров субпотоков выпадает из сопоставления, субпотоки считаются поврежденными и закрываются с использованием описанного в параграфе 3.7 процесса. MPTCP охраняет работоспособность и при потере некоторых sData ACK, но производительность будет снижаться по мере роста числа вырезаемых опций. Возникновение таких ситуаций на практике не предполагается, поскольку большинство ПУ пропускают или удаляют все опции.

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

NAT (Трансляторы сетевых адресов и портов)

Трансляторы [RFC3022] меняют адрес отправителя (зачастую и порт) в пакетах. Это означает, что хост не знает свой публичный адрес для сигнализации в MPTCP. Поэтому MPTCP разрешает неявное добавление адресов через опцию MP_JOIN и механизм согласования обеспечивает для попыток соединения с приватными адресами [RFC1918] (поскольку они аутентифицированы) организацию субпотоков лишь с нужными хостами. Явное удаление адресов выполняется по Address ID и позволяет не знать адрес отправителя.

Прокси-ускорители (Performance Enhancing Proxies или PEP)

PEP [RFC3135] могут заранее передавать пакеты ACK для повышения производительности. Однако MPTCP полагается на точность сигналов контроля перегрузки от конечного хоста, а не понимающие MPTCP устройства PEP не могут обеспечить таких сигналов. Поэтому MPTCP будет возвращаться к TCP или закрывать проблемный субпоток (параграф 3.7).

Нормализаторы трафика

Нормализаторы [norm] могут не допускать пропусков в порядковых номерах, а также кэшировать пакеты и повторно передавать данные. MPTCP в линии выглядит как обычный TCP и не передает разные данные с одним и тем же номером в субпотоке. В случае повторной передачи данные пойдут в том же субпотоке TCP, даже если они дополнительно повторены на уровне соединения с использованием другого субпотока.

Межсетевые экраны

МСЭ [RFC2979] могут использовать случайные начальные порядковые номера (Initial Sequence Number или ISN) для соединений TCP. В MPTCP используется относительная нумерация в Data Sequence Mapping для обхода этого. Подобно трансляторам NAT, МСЭ не разрешают множество входных соединений, поэтому MPTCP поддерживает передачу адресов (ADD_ADDR), позволяющую многоадресным хостам пригласить патнера, находящегося за МСЭ или NAT соединияться с ним через дополнительны интерфейс.

Системы детектирования и предотвращения вторжений (IDS/IPS)

Эти системы наблюдают в потоках пакетов определенные шаблоны и проверяют содержимое на предмет угроз из сети. Для MPTCP может потребоваться организация дополнительных путей и знающием о MPTCP устройствам IDS или IPS потребуется считывать маркеры MPTCP для сопоставления данных из множества субпотоков, чтобы получить полную картину трафика между устройствами. Без такого изменения IDS не будут видеть полной картины трафика и это увеличит риск прохождения вредоносных пакетов (ложный пропуск) и ошибочного отнесения пакетов к вредоносным (ложное срабатывание).

ПУ прикладного уровня

Такие устройства, как МСЭ с анализом содержимого, могут менять данные в субпотоках, например, переписывая URI в трафике HTTP. MPTCP будет замечать такие изменения при использовании контрольных сумм и закрывать соответствующие субпотоки, если другие субпотоки можно использовать. При влиянии на все субпотоки MPTCP будет возвращаться к TCP, позволяя ПУ менять содержимое пакетов. Осведомленные о MPTCP промежуточные устройства должны быть способны «подстраивать» содержимое и метаданные MPTCP, чтобы соединения не разрывались.

Кроме того, все классы ПУ могут влиять на трафик TCP, как указано ниже.

  • Многие классы МУ могут удалять опции TCP или отбрасывать пакеты с неизвестными опциями. Предполагается, что начального обмена SYN с опциями TCP достаточно для определения свойств пути. Если такие пакеты не проходят, MPTCP будет возвращаться к обычному TCP.

  • При разбиении и объединении сегментов (например, для выгрузки сегментации TCP) опции могут копироваться между пакетами или вырезаться. Data Sequence Mapping в MPTCP включает относительные порядковые номера субпотоков вместо использования порядковых номеров в сегменте. Это обеспечивает независимость от нумерации в пакетах.

  • Некоторые ПУ могут сокращать окно приема на уровне субпотока. MPTCP использует на уровне данные максимальное из окон субпотоков, сохраняя окна самих субпотоков.

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

Этот документ отменяет [RFC6824], поэтому обновлено несколько реестров IANA, ссылающихся на отмененный документ. Эти вопросы рассмотрены в последующих параграфах.

7.1. Номера опций TCP

Агентство IANA обновило реестр TCP Option Kind Numbers, указав этот документ для Multipath TCP (таблица 1).

Таблица 1. Номер опции TCP.

Тип

Размер

Назначение

Документ

30

N

Multipath TCP (MPTCP)

RFC 8684

7.2. Субтипы опции MPTCP

Субреестр 4-битовых субтипов опций MPTCP Option Subtypes в реестре Transmission Control Protocol (TCP) Parameters, был определен в [RFC6824]. Поскольку [RFC6824] относится к категории Experimental RFC, а не Standards Track RFC и в субреестр не было добавлено других записей, указывающих на [RFC6824], агентство IANA заменило имеющийся реестр содержимым таблицы 2 с представленным ниже примечанием.

Этот реестр задает субтипы опций MPTCP для MPTCP v1, отменяющего экспериментальный протокол MPTCP v0. Субтипы MPTCP v0 указаны в [RFC6824].

Таблица 2. Субтипы опции MPTCP.

Значение

Символ

Назначение

Документ

0x0

MP_CAPABLE

Поддержка множества путей

RFC 8684, параграф 3.1

0x1

MP_JOIN

Добавление в соединение

RFC 8684, параграф 3.2

0x2

DSS

Data Sequence Signal (Data ACK и Data Sequence Mapping)

RFC 8684, параграф 3.3

0x3

ADD_ADDR

Добавление адреса

RFC 8684, параграф 3.4.1

0x4

REMOVE_ADDR

Удаление адреса

RFC 8684, параграф 3.4.2

0x5

MP_PRIO

Смена приоритета субпотока

RFC 8684, параграф 3.3.8

0x6

MP_FAIL

Возврат к TCP

RFC 8684, параграф 3.7

0x7

MP_FASTCLOSE

Ускоренное закрытие

RFC 8684, параграф 3.5

0x8

MP_TCPRST

Сброс субпотока

RFC 8684, параграф 3.6

0xf

MP_EXPERIMENTAL

Резерв для частного применения

Значения 0x9 — 0xe в настоящее время не выделены, а 0xf зарезервировано для частных экспериментов (ее использование может быть задано в будущих спефикациях). Выделение значений в этом реестре происходит по процедуре Standards Action, определенной в [RFC8126]. Назначение включает символьное имя субтипа MPTCP, значение и ссылку на спецификацию.

7.3. Алгоритмы согласования MPTCP

Субреестр MPTCP Handshake Algorithms в реестре Transmission Control Protocol (TCP) Parameters, был определен в [RFC6824]. Поскольку [RFC6824] относится к категории Experimental RFC, а не Standards Track RFC и в субреестр не было добавлено других записей, указывающих на [RFC6824], агентство IANA заменило имеющийся реестр содержимым таблицы 3 с представленным ниже примечанием.

Этот реестр задает субтипы опций MPTCP для MPTCP v1, отменяющего экспериментальный протокол MPTCP v0. Субтипы MPTCP v0 указаны в [RFC6824].

Таблица 3. Алгоритмы согласования MPTCP.

Флаг

Назначение

Документ

A

Требуется контрольная сумма

RFC 8684, параграф 3.1

B

Расширяемость

RFC 8684, параграф 3.1

C

Не пытаться организовать новые субпотоки с этого адреса отправителя

RFC 8684, параграф 3.1

D-G

Не выделены

H

HMAC-SHA256

RFC 8684, параграф 3.2

Отметим, что значения битов D — H могут зависеть от бита B с учетом определения параметра Extensibility в будущих спецификациях (см. параграф 3.1).

Выделение значений в этом реестре происходит по процедуое Standards Action, заданной в [RFC8126]. Назначение включает значение флага, символьное имя алгоритма и ссылку на спецификацию.

7.4. Коды причин MP_TCPRST

Субреестр MPTCP MP_TCPRST Reason Codes создан IANA в реестре Transmission Control Protocol (TCP) Parameters для кодов причины в сообщениях MP_TCPRST (параграф 3.6). Начальные значения указаны в таблице 4, а выделение новых значений будет происходить по процедуре Specification Required, заданной в [RFC8126]. Назначение включает значение кода, краткое описание и ссылку на спецификацию. Максимальное значение кода — 0xff.

Таблица 4. Коды причин MP_TCPRST.

Код

Значение

Документ

0x00

Неуказанная ошибка

RFC 8684, параграф 3.6

0x01

Ошибка MPTCP

RFC 8684, параграф 3.6

0x02

Нехватка ресурсов

RFC 8684, параграф 3.6

0x03

Административный запрет

RFC 8684, параграф 3.6

0x04

Слишком много ожидающих данных

RFC 8684, параграф 3.6

0x05

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

RFC 8684, параграф 3.6

0x06

Влияние промежуточных усттройств

RFC 8684, параграф 3.6

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

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

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

[RFC0793] Postel, J., «Transmission Control Protocol», STD 7, RFC 793, DOI 10.17487/RFC0793, September 1981, <https://www.rfc-editor.org/info/rfc793>.

[RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, «HMAC: Keyed-Hashing for Message Authentication», RFC 2104, DOI 10.17487/RFC2104, February 1997, <https://www.rfc-editor.org/info/rfc2104>.

[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>.

[RFC5961] Ramaiah, A., Stewart, R., and M. Dalal, «Improving TCP’s Robustness to Blind In-Window Attacks», RFC 5961, DOI 10.17487/RFC5961, August 2010, <https://www.rfc-editor.org/info/rfc5961>.

[RFC6234] Eastlake 3rd, D. and T. Hansen, «US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF)», RFC 6234, DOI 10.17487/RFC6234, May 2011, <https://www.rfc-editor.org/info/rfc6234>.

[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>.

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

[deployments] Bonaventure, O. and S. Seo, «Multipath TCP Deployments», IETF Journal 2016, November 2016, <https://www.ietfjournal.org/multipath-tcp-deployments/>.

[howhard] Raiciu, C., Paasch, C., Barre, S., Ford, A., Honda, M., Duchene, F., Bonaventure, O., and M. Handley, «How Hard Can It Be? Designing and Implementing a Deployable Multipath TCP», Usenix Symposium on Networked Systems Design and Implementation 2012, April 2012, <https://www.usenix.org/conference/nsdi12/technical-sessions/presentation/raiciu>.

[norm] Handley, M., Paxson, V., and C. Kreibich, «Network Intrusion Detection: Evasion, Traffic Normalization, and End-to-End Protocol Semantics», Usenix Security Symposium 2001, August 2001, <https://www.usenix.org/legacy/events/sec01/full_papers/handley/handley.pdf>.

[RFC1122] Braden, R., Ed., «Requirements for Internet Hosts — Communication Layers», STD 3, RFC 1122, DOI 10.17487/RFC1122, October 1989, <https://www.rfc-editor.org/info/rfc1122>.

[RFC1918] Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G. J., and E. Lear, «Address Allocation for Private Internets», BCP 5, RFC 1918, DOI 10.17487/RFC1918, February 1996, <https://www.rfc-editor.org/info/rfc1918>.

[RFC2018] Mathis, M., Mahdavi, J., Floyd, S., and A. Romanow, «TCP Selective Acknowledgment Options», RFC 2018, DOI 10.17487/RFC2018, October 1996, <https://www.rfc-editor.org/info/rfc2018>.

[RFC2979] Freed, N., «Behavior of and Requirements for Internet Firewalls», RFC 2979, DOI 10.17487/RFC2979, October 2000, <https://www.rfc-editor.org/info/rfc2979>.

[RFC2992] Hopps, C., «Analysis of an Equal-Cost Multi-Path Algorithm», RFC 2992, DOI 10.17487/RFC2992, November 2000, <https://www.rfc-editor.org/info/rfc2992>.

[RFC3022] Srisuresh, P. and K. Egevang, «Traditional IP Network Address Translator (Traditional NAT)», RFC 3022, DOI 10.17487/RFC3022, January 2001, <https://www.rfc-editor.org/info/rfc3022>.

[RFC3135] Border, J., Kojo, M., Griner, J., Montenegro, G., and Z. Shelby, «Performance Enhancing Proxies Intended to Mitigate Link-Related Degradations», RFC 3135, DOI 10.17487/RFC3135, June 2001, <https://www.rfc-editor.org/info/rfc3135>.

[RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker, «Randomness Requirements for Security», BCP 106, RFC 4086, DOI 10.17487/RFC4086, June 2005, <https://www.rfc-editor.org/info/rfc4086>.

[RFC4987] Eddy, W., «TCP SYN Flooding Attacks and Common Mitigations», RFC 4987, DOI 10.17487/RFC4987, August 2007, <https://www.rfc-editor.org/info/rfc4987>.

[RFC5681] Allman, M., Paxson, V., and E. Blanton, «TCP Congestion Control», RFC 5681, DOI 10.17487/RFC5681, September 2009, <https://www.rfc-editor.org/info/rfc5681>.

[RFC6181] Bagnulo, M., «Threat Analysis for TCP Extensions for Multipath Operation with Multiple Addresses», RFC 6181, DOI 10.17487/RFC6181, March 2011, <https://www.rfc-editor.org/info/rfc6181>.

[RFC6182] Ford, A., Raiciu, C., Handley, M., Barre, S., and J. Iyengar, «Architectural Guidelines for Multipath TCP Development», RFC 6182, DOI 10.17487/RFC6182, March 2011, <https://www.rfc-editor.org/info/rfc6182>.

[RFC6356] Raiciu, C., Handley, M., and D. Wischik, «Coupled Congestion Control for Multipath Transport Protocols», RFC 6356, DOI 10.17487/RFC6356, October 2011, <https://www.rfc-editor.org/info/rfc6356>.

[RFC6528] Gont, F. and S. Bellovin, «Defending against Sequence Number Attacks», RFC 6528, DOI 10.17487/RFC6528, February 2012, <https://www.rfc-editor.org/info/rfc6528>.

[RFC6824] Ford, A., Raiciu, C., Handley, M., and O. Bonaventure, «TCP Extensions for Multipath Operation with Multiple Addresses», RFC 6824, DOI 10.17487/RFC6824, January 2013, <https://www.rfc-editor.org/info/rfc6824>.

[RFC6897] Scharf, M. and A. Ford, «Multipath TCP (MPTCP) Application Interface Considerations», RFC 6897, DOI 10.17487/RFC6897, March 2013, <https://www.rfc-editor.org/info/rfc6897>.

[RFC7323] Borman, D., Braden, B., Jacobson, V., and R. Scheffenegger, Ed., «TCP Extensions for High Performance», RFC 7323, DOI 10.17487/RFC7323, September 2014, <https://www.rfc-editor.org/info/rfc7323>.

[RFC7413] Cheng, Y., Chu, J., Radhakrishnan, S., and A. Jain, «TCP Fast Open», RFC 7413, DOI 10.17487/RFC7413, December 2014, <https://www.rfc-editor.org/info/rfc7413>.

[RFC7430] Bagnulo, M., Paasch, C., Gont, F., Bonaventure, O., and C. Raiciu, «Analysis of Residual Threats and Possible Fixes for Multipath TCP (MPTCP)», RFC 7430, DOI 10.17487/RFC7430, July 2015, <https://www.rfc-editor.org/info/rfc7430>.

[RFC8041] Bonaventure, O., Paasch, C., and G. Detal, «Use Cases and Operational Experience with Multipath TCP», RFC 8041, DOI 10.17487/RFC8041, January 2017, <https://www.rfc-editor.org/info/rfc8041>.

[RFC8126] Cotton, M., Leiba, B., and T. Narten, «Guidelines for Writing an IANA Considerations Section in RFCs», BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017, <https://www.rfc-editor.org/info/rfc8126>.

[TCPLO] Ramaiah, A., «TCP option space extension», Work in Progress, Internet-Draft, draft-ananth-tcpm-tcpoptext-00, 26 March 2012, <https://tools.ietf.org/html/draft-ananth-tcpm-tcpoptext-00>.

Приложение A. Использование опций TCP

Пространство опций TCP ограничено по причине малого размера поля Data Offset в заголовке TCP (4 бита), задающего размер заголовка TCP в 32-битовых. Стандартный заголовок TCP занимает 20 байтов и для опций остается лишь 40, из которых многие уже заняты такими опциями, как временные метки и SACK.

Было рассмотрено общепринятое использование опций TCP в пакетах SYN, данных и «чистых» подтверждениях ACK и обнаружено достаточно места для включения описанных в документе опций.

Пакеты SYN обычно включают опции MSS5 (4 байта), масштаб окна (3 байта), возможность использовать SACK (2 байта) и временную метку (10 байтов), что в сумме дает 19 байтов. Некоторые операционные системы дополняют каждую опцию до границы слова, что может увеличить размер до 24 байтов (известно, что Windows XP и Mac OS X используют лополнение, Linux — нет). В результате оптимистичная оценка дает 21 свободный байт или 16 при использовании дополнения. В любом случае для пакетов SYN опция MP_CAPABLE (12 байтов) или MP_JOIN (12 или 16 байтов) помещается в заголовок.

Отметим, что, благодаря применению 64-битовых номеров на уровне данных, MPTCP не требует опции timestamp для защиты от переполнения (wrap) порядковых номеров (с помощью механизма PAWS6 [RFC7323]), поскольку достижение максимального номера значительно менее вероятно. Этот вопрос оставлен для дальнейшего изучения.

Пакеты TCP обычно содержат временную метку, занимающую 10 байтов (12 с заполнением). Это оставляет для других опций 30 байтов (28 при заполнении). Опция DSS меняет размер в зависимости от (1) включения Data Sequence Mapping, DATA_ACK или обоих, (2) передачи 4- или 8-октетных номеров и (3) наличия контрольной суммы. Максимальный размер опции DSS составляет 28 байтов, что позволяет ее включать в оставшееся пространство. Однако, если соединение не является сразу двухсторонним и широкополосным, такой размер опции в каждом пакете маловероятен.

Для опции DSS не требуется включать всякий раз Data Sequence Mapping и DATA_ACK, а во многих случаях можно чередовать их (пока отображение включает данные для передачи в следующем пакете). Можно также чередовать 4- и 8-байтовые номера в каждой опции.

При организации субпотока и соединения опций MPTCP передается также в третьем пакете (ACK). MP_CAPABLE (20 байтоы) или MP_JOIN (24 байта) помещается в оставшееся место заголовка.

«Чистые» ACK в TCP обычно включают лишь временную метку (10 байтов). Протоколу Multipath TCP здесь обычно требуется указать лишь DATA_ACK (не более 12 байтов). Иногда пакеты ACK включают информацию SACK. В зависимости от числа потерянных пакетов SACK может занимать все пространство опций. Если нужно включить DATA_ACK, вероятно потребуется снизить число блоков SACK. Однако присутствие DATA_ACK вряд ли потребуется при использовании SACK, поскольку до повторной передачи хотя бы некоторых блоков SACK кумулятивное подтверждение ACK на уровне соединения не перемещается вперед (или делает это при повторах по другому пути, который можно использовать для передачи DATA_ACK).

Опция ADD_ADDR может иметь размер от 16 до 30 байтов в зависимости от (1) используемого протокола (IPv4 или IPv6) и (2) присутствия номера порта. Маловероятно, что такая сигнализация поместится в пакет данных, хотя при наличии места ее могжно включить. Рекомендуется не применять дубликаты ACK с другими данными или опциями для передачи этих сигналов. Отметим, что это послужило причиной того, что дубликаты ACK не считаются в MPTCP признаком перегрузки.

Приложение B. TCP Fast Open и MPTCP

Экспериментальное расширение TCP Fast Open (TFO) [RFC7413] разработано для обеспечения возможности передачи данных на один интервал RTT раньше, чем в обычном TCP. Это считается важным преимуществом, поскольку широко распространены очень короткие соединения, особенно для запросов и откликов HTTP. Механизм реализуется путем отправки сегмента SYN с данными приложения и обеспечения принимающей стороне возможности передать ответные данные вслед за SYN/ACK. [RFC7413] защищает этот механизм за счет использования новой опции TCP, включающей значение cookie, согласованное в предыдущем соединении.

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

B.1. TFO Cookie в MPTCP

При первом подключении инициатора TFO к прослушивающей стороне он не может сразу включить данные в SYN по соображениям безопасности [RFC7413]. Вместо этого он запрашивает cookie для использования в последующих соединениях. Это делается с помощью опции запроса и возврата TCP, размером 2 и 6-18 байтов (в зависимости от размера cookie).

TFO и MPTCP можно объединить, если общий размер всех опций в заголовке TCP не превысит 40 байтов.

  • В пакетах SYN протокол MPTCP использует 4-байтовую опцию MP_CAPABLE. Суммарный размер опций MPTCP и TFO составляет 6 байтов. При типичном для TCP наборе опций SYN в 19 байтов (24 при выравнивании по границе слова) остается достаточно места для опции MP_CAPABLE и запроса TFO cookie.

  • В пакетах SYN + ACK протокол MPTCP использует 12-байтовую опцию MP_CAPABLE, но опция TFO может достигать 18 байтов. Поскольку размер поля опций явно превышается, принимающей стороне для решения проблемы остается лишь сокращение размера cookie. Например, при использовании 19 байтов для классических опций TCP максимальный размер cookie составит 7 байтов. Отметим, что для пакета SYN такое же ограничение применимо к последующим соединениям (поскольку инициатор возвращает значение cookie). Если снижение размера cookie не приемлемо с точки зрения безопасности, принимающая сторона может сократить другие опции TCP, опустив временные метки TCP (как указано в Приложении A).

B.2. Отображение порядковых номеров при TFO

В фазе организации соединения TCP протокол MPTCP использует обмен ключами, применяемыми для генерации начальных порядковых номеров IDSN. В частности, SYN с опцией MP_CAPABLE занимает первый октет пространства номером. При использовании TFO одним из способов передачи данных в SYN является неявное отображение DSS, охватывающее сегмент SYN (поскольку в пакете SYN нет места для включения опции DSS). Проблема этого подхода заключается в том, что при изменении промежуточным устройством данных TFO этого не увидит MPTCP по причине отсутствия контрольной суммы DSS. Например, знающее TCP (но не MPTCP) промежуточное устройство может вставить байты в начало потока, скорректировав контрольную сумму TCP и порядковые номера. При неявном отображении эта информация будет давать инициатору и принимающей стороне разное отображение DSS и это невозможно увидеть по причине отсутствия контрольной суммы DSS.

Для решения проблемы данные TFO не должны считаться частью пространства порядковых номеров соединения. SYN с опцией MP_CAPABLE по-прежнему занимает первый октет пространства, но затем второй октет занимает первый байт данных, не относящихся к TFO. Это гарантирует, что при согласованном использовании контрольной суммы DSS все данные в пространстве номеров соединения будут учтены в контрольной сумме. Отметим также, что это не влечет потери функциональности, поскольку данные TFO передаются лишь в первом субпотоке до попыток создания новых субпотоков в соединении.

B.3. Примеры организации соединений

Ниже приведено несколько примеров организации соединения при использовании TFO с протоколом MPTCP.

Перед отправкой инициатором данных вместе с SYN он должен запросить у принимающей стороны значение cookie, как показано на рисунке 18. Отметим, что на этом и последующих рисунках номер и размер указаны в форме Seq(Length), например, S. 0(0). Это делается путем обычного использования опций TFO и MPTCP.

Инициатор                                                   Приемник
    |                                                           |
    |   S Seq=0(Length=0) <MP_CAPABLE>, <TFO cookie request>    |
    | --------------------------------------------------------> |
    |                                                           |
    |   S. 0(0) ack 1 <MP_CAPABLE>, <TFO cookie>                |
    | <-------------------------------------------------------- |
    |                                                           |
    |   .  0(0) ack 1 <MP_CAPABLE>                              |
    | --------------------------------------------------------> |
    |                                                           |

Рисунок 18. Запрос cookie.


После этого полученное значение cookie можно применять в TFO, как показано на рисунке 19. В этом примере инициатор сначала передает 20 байтов в SYN. Принимающая сторона сразу же отвечает 100 байтами, следующими за SYN-ACK, на которые инициатор отвечает еще 20 байтами. Отметим, что последний сегмент на рисунке имеет номер TCP 21, тогда как DSS субпотока имеет номер 1 (поскольку данные TFO не являются частью пространства порядковых номеров данных, как отмечено в Приложении B.2).

Инициатор                                                   Приемник
    |                                                           |
    |    S  0(20) <MP_CAPABLE>, <TFO cookie>                    |
    | --------------------------------------------------------> |
    |                                                           |
    |    S. 0(0) ack 21 <MP_CAPABLE>                            |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  1(100) ack 21 <DSS ack=1 seq=1 ssn=1 dlen=100>      |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  21(0) ack 1 <MP_CAPABLE>                            |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  21(20) ack 101 <DSS ack=101 seq=1 ssn=1 dlen=20>    |
    | --------------------------------------------------------> |
    |                                                           |

Рисунок 19. Приемник с поддержкой TFO.


На рисунке 20 принимающая сторона не поддерживает TFO. Инициатор видит отсутствие состояния на приемной стороне (нет ACK для данных) и передает MP_CAPABLE в третьем пакете, чтобы приемная сторона могла создать контектс MPTCP в конце процесс согласования. Сейчас при повторе данных TFO они становятся частью Data Sequence Mapping, поскольку фактически передаются после организации соединения.

Инициатор                                                   Приемник
    |                                                           |
    |    S  0(20) <MP_CAPABLE>, <TFO cookie>                    |
    | --------------------------------------------------------> |
    |                                                           |
    |    S. 0(0) ack 1 <MP_CAPABLE>                             |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  1(0) ack 1 <MP_CAPABLE>                             |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  1(20) ack 1 <DSS ack=1 seq=1 ssn=1 dlen=20>         |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  0(0) ack 21 <DSS ack=21 seq=1 ssn=1 dlen=0>         |
    | <-------------------------------------------------------- |
    |                                                           |

Рисунок 20. Приемник без поддержки TFO.

Инициатор                                                   Приемник
    |                                                           |
    |    S  0(1000) <MP_CAPABLE>, <TFO cookie>                  |
    | --------------------------------------------------------> |
    |                                                           |
    |    S. 0(0) ack 501 <MP_CAPABLE>                           |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  501(0) ack 1 <MP_CAPABLE>                           |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  501(500) ack 1 <DSS ack=1 seq=1 ssn=1 dlen=500>     |
    | --------------------------------------------------------> |
    |                                                           |

Рисунок 21. Частичное подтверждение данных.


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

Приложение C. Блоки управления

Концептуально соединение MPTCP можно представить как блок управления протоколом MPTCP (protocol control block или PCB), содержащий несколько переменных, которые отражают процесс организации и состояние соединения MPTCP и набор связанных с этим блоков управления TCP для созданных в соединении субпотоков.

В RFC 793 [RFC0793] задано несколько переменных состояния и здесь по возможности применяется терминология RFC 793 для описания переменных состояния MPTCP.

C.1. Блок управления MPTCP

Блок управления MPTCP содержит описанные ниже переменные состояния на уровне соединения в целом.

C.1.1. Аутентификация и метаданные

Local.Token (32 бита)

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

Local.Key (64 бита)

Ключ, переданный локальным хостом для этого соединения MPTCP.

Remote.Token (32 бита)

Маркер, выбранный удаленным хостом для данного соединения MPTCP (генерируется из удаленного ключа).

Remote.Key (64 бита)

Ключ, выбранный локальным хостом для этого соединения MPTCP.

MPTCP.Checksum (flag)

Флаг, устанавливаемый (true) при установке любой из сторон бита A в опции MP_CAPABLE, переданной при органиации соединения. В противном случае флаг сброшен (false). Установка этого флага требует расчета контрольной суммы во всех опциях DSS.

C.1.2. Передающая сторона

SND.UNA (64 бита)

Порядковый номер следующего байта для подтверждения на уровне соединения MPTCP. Переменная обновляется при получении опции DSS с DATA_ACK.

SND.NXT (64 бита)

Порядковый номер передаваемого следующим байта. Переменная служит значением DSN в опции DSS.

SND.WND (32 бита)

Окно передачи. 32 бита при использовании функций RFC 7323 и 16 — в остальных случаях. MPTCP поддерживает окно передачи на уровне MPTCP, которое является общим для всех субпотоков. Субпотоки применяют SND.WND для расчета значения SEQ.WND в каждом передаваемом сегменте.

C.1.3. Приемная сторона

RCV.NXT (64 бита)

Порядковый номер байта, ожидаемого в соединении MPTCP. Переменная изменяется при получении данных с нарушением порядка. Значение RCV.NXT применяется для значений DATA_ACK, передаваемых в обции DSS всех субпотоков.

RCV.WND (32 бита)

Окно приема на уровне соединения, которое принимает максимальное из значений RCV.WND среди всех субпотоков. 32 бита при использовании функций RFC 7323 и 16 — в остальных случаях.

C.2. Блоки управления TCP

Блок управления MPTCP содержит также список блоков управления TCP, связанных с соединением MPTCP.

Отметим, что блоки управления TCP всех субпотоков не включают переменных RCV.WND и SND.WND, поскольку они поддерживаются на уровне соединения, а не субпотока.

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

C.2.1. Передающая сторона

SND.UNA (32 бита)

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

SND.NXT (32 бита)

Порядковый номер следующего байта, который будет передан в субпотоке. SND.NXT служит для установки SEG.SEQ при передаче следующего сегмента.

C.2.2. Приемная сторона

RCV.NXT (32 бита)

Порядковый номер следующего байта, который ожидается в субпотоке. Переменная обновляется при получении сегментов с нарушением порядка. Значение RCV.NXT копируется в поле SEG.ACK следующего сегмента, передаваемого в субпоток.

RCV.WND (32 бита)

Окно приема на уровне субпотока, обновляемое значением поля окна из сегментов, принятых в субпотоке. 32 бита при использовании функций RFC 7323 и 16 — в остальных случаях.

Приложение D. Конечный автомат состояний

На рисунке 22 показан конечный автомат (Finite State Machine или FSM) для закрытия соединения. Рисунок показывает взаимодействие сигнала DATA_FIN (указан на рисунке флагом DFIN в DATA_ACK) (1) с FIN на уровне потоков и (2) переключение break-before-make (прервать до организации) между субпотоками.

                             +---------+
                             | M_ESTAB |
                             +---------+
                    M_CLOSE    |     |    rcv DATA_FIN
                     -------   |     |    -------
+---------+       snd DATA_FIN /       \ snd DATA_ACK[DFIN] +-------+
|  M_FIN  |<-----------------           ------------------->|M_CLOSE|
| WAIT-1  |---------------------------                      |  WAIT |
+---------+               rcv DATA_FIN \                    +-------+
  | rcv DATA_ACK[DFIN]         ------- |                   M_CLOSE |
  | --------------        snd DATA_ACK |                   ------- |
  | CLOSE все субпотоки                |              snd DATA_FIN |
  V                                    V                           V
+-----------+              +-----------+                 +----------+
|M_FINWAIT-2|              | M_CLOSING |                 |M_LAST-ACK|
+-----------+              +-----------+                 +----------+
  |              rcv DATA_ACK[DFIN] |           rcv DATA_ACK[DFIN] |
  | rcv DATA_FIN     -------------- |               -------------- |
  |  -------    CLOSE все субпотоки |          CLOSE все субпотоки |
  | snd DATA_ACK[DFIN]              V            delete MPTCP PCB  V
  \                          +-----------+                 +--------+
    ------------------------>|M_TIME WAIT|---------------->|M_CLOSED|
                             +-----------+                 +--------+
                                        все субпотоки CLOSED
                                            ------------
                                        delete MPTCP PCB

Рисунок 22. Конечный автомат для закрытия соединения.


Приложение E. Отличия от RFC 6824

В этом приложении перечислены основные технические различия между [RFC6824], где задан протокол MPTCP v0 и данным документом, который отменяет [RFC6824] и задает MPTCP v1. Отметим, что новая спецификация не совместима с [RFC6824].

  • Этот документ включает опыт реализаций, развертывания и экспериментов, собранный в документе Use Cases and Operational Experience with Multipath TCP [RFC8041] и статье в IETF Journal Multipath TCP Deployments [deployments].

  • Инициирование соединения с обменом опцией MPTCP MP_CAPABLE отличается от [RFC6824]. SYN больше не содержит ключ инициатора для сокращения размера опции MP_CAPABLE в пакете SYN и предотвращения дублирования передачи ключевого материала.

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

  • В поле Flags опции MP_CAPABLE выделен бит C, указывающий, что отправитель опции не будет воспринимать дополнительные субпотоки MPTCP по адресу и порту. Это повышает эффективность, например, при размещении отправителя за NAT.

  • В поле Flags опции MP_CAPABLE бит H указывает использование алгоритма HMAC-SHA256 (вместо HMAC-SHA1).

  • Инициирование соединения также определяет процедуру согласования версий для реализаций, поддерживающих v0 [RFC6824] и v1 (данный документ).

  • Применяется алгоритм HMAC-SHA256 (взамен HMAC-SHA1), обеспечивающий лучшую защиту. Алгоритм служит для генерации маркеров в MP_JOIN и ADD_ADDR, а также для установки IDSN.

  • Имеется новая опция на уровне субпотока для указания причины отправки RST в субпотоке MP_TCPRST (параграф 3.6), которая может помочь реализации при решении вопроса о повторении попыток соединения.

  • Опция MP_PRIO (параграф 3.3.8), служащая для изменения приоритета субпотока, больше не включает поле AddrID, которое позволяло изменить приоритет для другого субпотока. Однако было выяснено, что опция может применяться для MITM-атак7 для перенаправления трафика на свой путь, а опция MP_PRIO не включает маркера или иного механизма защиты.

  • Опция ADD_ADDR (параграф 3.4.1), служащая для информирования партнера о другом возможном адресе, отличается в нескольких аспектах. Сейчас она включает HMAC для добавленного адреса, что улучшает защиту. Кроме того, добавлена надежная доставка опции ADD_ADDR — поле IPVer заменено полем флагов, один из которых (E) испольузется как «эхо» и хост может сообщить о получении опции.

  • Этот документ описывает дополнительный вариант процедуры Fast Close за счет передачи опции MP_FASTCLOSE в RST через все субпотоки. Это позволяет хосту сразу разорвать субпотоки и соединение.

  • Агентство IANA зарезервировало субтип опции MPTCP со значением 0xf для частных применений (Private Use, параграф 7.2). Этот документ не задает способа использования данного значения.

  • В документ добавлено Приложение B с обсуждением одновременного использования опций MPTCP и TFO в одном пакете.

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

Авторы признательные Sebastien Barre и Andrew McDonald за существенный вклад в этот документ.

Спасибо Iljitsch van Beijnum, Lars Eggert, Marcelo Bagnulo, Robert Hancock, Pasi Sarolahti, Toby Moncaster, Philip Eardley, Sergio Lembo, Lawrence Conroy, Yoshifumi Nishida, Bob Briscoe, Stein Gjessing, Andrew McGregor, Georg Hampel, Anumita Biswas, Wes Eddy, Alexey Melnikov, Francis Dupont, Adrian Farrel, Barry Leiba, Robert Sparks, Sean Turner, Stephen Farrell, Martin Stiemerling, Gregory Detal, Fabien Duchene, Xavier de Foy, Rahul Jadhav, Klemens Schragel, Mirja Kühlewind, Sheng Jiang, Alissa Cooper, Ines Robles, Roman Danyliw, Adam Roach, Eric Vyncke, Ben Kaduk за рецензии и вклад в работу.

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

Alan Ford

Pexip

Email: alan.ford@gmail.com

Costin Raiciu

University Politehnica of Bucharest

Splaiul Independentei 313

Bucharest

Romania

Email: costin.raiciu@cs.pub.ro

Mark Handley

University College London

Gower Street

London

WC1E 6BT

United Kingdom

Email: m.handley@cs.ucl.ac.uk

Olivier Bonaventure

Université catholique de Louvain

Pl. Ste Barbe, 2

1348 Louvain-la-Neuve

Belgium

Email: olivier.bonaventure@uclouvain.be

Christoph Paasch

Apple, Inc.

Cupertino, CA

United States of America

Email: cpaasch@apple.com

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

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

nmalykh@protokols.ru

1Internet Engineering Task Force.

2Internet Engineering Steering Group.

3Equal-Cost Multipath — множество равноценных путей.

4Hash-based Message Authentication Code — основанный на хэшировании код аутентификации сообщения.

5Maximum Segment Size — максимальный размер сегмента.

6Protection Against Wrapped Sequences — защита для случаев перехода порядкового номера через максимум.

7Man-in-the-middle — перехват и изменение данных в пути с участием человека.

Рубрика: RFC | Комментарии к записи RFC 8684 TCP Extensions for Multipath Operation with Multiple Addresses отключены