Введение в SCTP
Здравствуйте, уважаемые читатели!
SCTP — достаточно специфичный протокол, о котором, наверняка, многие не слышали. Тем не менее, он более чем актуален в некоторых областях, одна из них — доставка контрольных сообщений в LTE сетях.
Я относительно недавно присоединился к команде, занимающейся разработкой ПО для базовых станций. Ранее я не сталкивался с данным протоколом, поэтому решил разобраться с ним более подробно и столкнулся со следующей проблемой — относительно малое количество источников, посвященных данной теме. Несомненно, они есть, и я обязательно перечислю те из них, которыми мне пришлось воспользоваться, в данной статье.
В общем, главная мотивация — желание написать небольшую вводную на таком ресурсе как habr для более легкого старта у новичков.
Также, отдельно хочу обратить внимание — в первую очередь упор идет на моменты, на которых можно споткнуться, если о них не знать.
Первый взгляд
После первой (и поверхностной) попытки загуглить складывается следующее впечатление о данном протоколе:
протокол с установлением соединения
доставка данных сообщениями
«надежная» доставка
возможность указать ppid при отправке — грубо говоря, указываем идентификатор используемого поверх sctp протокола
упорядоченность сообщений в рамках неких внутренних streams (ремарка — чтобы избежать путаницы, в дальнейшем потоки sctp будут упоминаться как streams, а потоки выполнения, в случае их упоминания, как threads). Streams позволяют с одной стороны, обеспечить упорядочивание данных в рамках stream, с другой — распределить независимые данные между разными streams
ряд других преимуществ, которые в рамках введения все же не будут рассмотрены
В целом, для поверхностного впечатления этой информации даже может хватить. Однако, для полноценного использования данного протокола этой информации все же недостаточно. Предлагаю копнуть чуть глубже. Для этих целей воспользуемся книгой «UNIX Разработка сетевых приложений» за авторством У. Р. Стивенса. Данная книга посвящена не только данному протоколу, но информация по sctp в ней представлена в достаточном объеме. Также, периодически я поглядывал в RFC 6458.
Приглядываемся повнимательнее
Первая глава, посвященная данному протоколу, начинается с тривиального сравнения протоколов sctp и tcp. Общий посыл следующий — возможность относительно легкого перехода с tcp на sctp при необходимости (оба протокола предоставляют интерфейс типа «один‑к-одному»). Однако, как говорится далее, при таком подходе возможности sctp раскрываются «не в полной мере». Для наиболее эффективного использования протокола следует воспользоваться интерфейсом типа «один‑ко‑многим».
Уже на данном этапе стоит немного остановиться и поразмышлять. Преимущества соединения один ко многим в целом очевидны — одним из них является отсутствие накладных расходов на обслуживание большого количества открытых сокетов. Также, с библиотеками и готовыми решениями для sctp все не очень хорошо, и использование соединения типа «один‑ко‑многим» в чем‑то может упростить нам жизнь (об этом немного поподробнее будет позже).
Если говорить про интерфейс типа «один‑ко‑многим», то ожидаемым поведением является (лично у меня) отправка данных без установления соединения (в конце концов, каким образом мы будем детектировать факт подключения нового клиента?). Однако, это все же не так. Рассмотрим такие механизмы данного протокола, как уведомления и ассоциации.
Ассоциации
При режиме «один‑к-одному» сокет это и есть ассоциация. При режиме «один‑ко‑многим» несколько узлов подключается к одному сокету на стороне сервера, каждый узел можно идентифицировать по некоторому целочисленному идентификатору ассоциации
Уведомления
SCTP поддерживает возможность обработки уведомления (по‑умолчанию включено только уведомление sctp_data_io_event, которое позволяет с каждой операцией чтения пользовательских данных получать структуру sctp_sndrcvinfo, которая позволяет извлекать некоторую информацию об отправителе. В частности, как будет показано далее, это поможет извлечь информацию о streams на стороне подключенного узла).
Виды уведомлений:
SCTP_ASSOC_CHANGE — изменение ассоциации (подключение или отключение узла)
SCTP_PEER_ADDR_CHANGE — изменение состояния одного из адресов подключенного узла (если кратко, sctp позволяет обеспечивать нечто вроде многоадресного подключения, но тут это рассматриваться не будет)
SCTP_REMOTE_ERROR — подключенный узел может отправить уведомление о некоторой ошибке с его стороны
SCTP_SEND_FAILED — уведомления о неудачной отправке. Как правило, причина в отказе ассоциации, и данное уведомление придет следующим
SCTP_SHUTDOWN_EVENT — уведомление о разрыве соединения
SCTP_PARTIAL_DELIVERY_EVENT — уведомление о частичной доставке. Большое сообщение может приходить кусками. Запомним данное уведомление
некоторые другие более специфичные уведомления. Стоит лишь сказать, что некоторые из них, например, в общем случае поддерживаются не всеми реализациями.
Описанные выше уведомления, с одной стороны, дают ответ на вопрос, как пользоваться данным протоколом в режиме «один‑ко‑многим». С другой стороны, то же уведомление о частичной доставке подсказывает, что этот протокол может быть несколько сложнее в использовании, чем казалось на первый взгляд. Но об этом подробнее несколько позже.
Каким образом получать уведомления? Следует воспользоваться функцией sctp_recv_msg
, которая позволит нам передать аргумент msg_flags
, по нему определить, что пришедшее сообщение является уведомлением и соответствующим образом его обработать.
Затруднения (очевидные и не очень)
После полученной информации становятся очевидными некоторые трудности в использовании данного протокола:
случай с частичной доставкой необходимо обрабатывать (сохранять пакеты в буфер и объединять в сообщение по мере готовности). В рамках сетей LTE подобный сценарий является маловероятным, но теоретически, подобное все же возможно. А посему, будьте готовы немного повозиться с буферами и их очисткой при различных возможных сценариях (как минимум, при уведомлении о разрыве соединения)
трудности с интеграцией с высокоуровневыми библиотеками. Тот же boost.asio не поддерживает работу с данным протоколом. Если возникнет необходимость обеспечить интеграцию, то это придется делать самому. Более того, так как тот же
msg_flags
можно заполучить именно через sctp‑шные функции, становится несколько неясно, как интегрировать данный функционал в случае необходимости его поддержки
На первый взгляд это единственные трудности использования этого протокола, но нет:
количество streams нужно согласовывать. Как было упомянуто выше, событие sctp_data_io_event позволяет извлекать информацию об отправителе. Что можно извлечь? Например, максимальное количество входящих и исходящих streams. (Данные параметры имеют стандартные величины, но их также можно настроить). Что будет если, например, использовать число streams для отправки больше чем число streams на получение у другого узла? Да ничего. Данные просто не уйдут (детали могут быть сложнее, но в целом — это некорректная ситуация).
как уже говорилось, некоторые уведомления возникают для одного и того же события, например, разрыв соединения при отправке данных приведет вначале к уведомлению о неудачной отправке, вслед за которым придет уведомление о разрыве соединения (честно говоря, я не берусь утверждать, что гарантирован именно данный порядок. По идее да. Это является критичным? Будьте добры прошерстить RFC). Не говоря уже о том, что, например, может возникнуть нужда обработать неудачную отправку в зависимости от передаваемых внутри данных. К счастью, для этих нужд уведомление о неудачной отправке предоставит вам сами неотправленные данные.
уведомления тоже могут доставляться частично (по‑идее, честно, пока не сталкивался). Все то же уведомление о неудачной отправке содержит поле с данными, которые могут превышать некоторый предел, что приведет к доставке частями.
когда возникнет нужда разбираться с событиями, могут возникнуть вопросы различного рода. Например, есть подписка на уведомление
sctp_send_failure_event
, а есть на уведомлениеsctp_send_failure_event_event
Ну и на закуску некоторая скорее теоретическая проблема (но не факт :) ):
все то же уведомление о неудачной отправке. Вы отправляете данные. Отправка не удается. Вам приходит сообщение, вы считываете информацию в буфер. Далее, по
msg_flags
вы определили, что это уведомление, и решили его обработать. Вот только полученные данные содержат не только отправленную вами информацию, но и некоторые служебные данные, пускай и в небольшом объеме. А теперь внимание, вопрос. Вы используете статические буферы заданного размера. Вы четко уверены, что отправляемые данные не будут превышать этот предел. Что будет, если звезды сойдутся, и размер полученных данных вместе со служебной информацией размер буфера превысит, а вы начнете это уведомление обрабатывать? Теоретически, выйдет что‑то очень веселое.
Тыкаем палкой в упорядоченность
Освежим известную информацию об упорядоченности - есть некоторые streams, в рамках которых отправляемые сообщения упорядочиваются.
Что может быть еще?
Неупорядоченные сообщения
Ну тут все просто — если нет необходимости в строгом порядке отправляемых данных, либо этот порядок гарантируется как‑то еще, то можно пометить сообщение как неупорядоченное.
Однако, важный момент — сообщение все равно отправляется в рамках какого‑то stream, иначе мы бы не смогли нормально обработать его частичную доставку (опять она...)
Режимы смешивания при частичной доставке
Тут все интересно — в случае частично й доставки сообщение доставляется фрагментами. Что будет, если в момент получения сообщения по частям влезут фрагменты другого сообщения, а также что по этому поводу говорит стандарт? (смотрим 8.1.20. Get or Set Fragmented Interleave (SCTP_FRAGMENT_INTERLEAVE)
в RFC 6458)
Существует 3 вида смешивания:
уровень 0. Если происходит частичная доставка, то данное сообщение блокирует все ассоциации сокета
уровень 1 (режим по-умолчанию). Если происходит частичная доставка, то данное сообщение блокирует все остальные сообщения в рамках текущей ассоциации
уровень 2. Если происходит частичная доставка, то никаких блокировок нет (если быть более точным, то блокировка возникнет, но в рамках stream)
На что влияют данные режимы упорядоченности? Чем выше уровень, тем меньше разные ассоциации и потоки мешают друг другу, но тем сложнее механизм буферов. Также, есть следующие нюансы:
если используется соединение «один‑к-одному», то режимы 0 и 1 эквивалентны (одна ассоциация на один сокет)
если используется режим 0, то достаточно одного буфера
если используется режим 1, то нужно отслеживать идентификаторы ассоциации
если используется режим 2, то нужно отслеживать и идентификаторы ассоциации, и идентификаторы потока
если возможности streams особо не используются (а такое возможно), то режим 2 будет излишним (лучше соответствующим образом настроить конфигурацию streams)
Великое множество параметров конфигурации соединения
Данный протокол позволяет настроить очень большое количество параметров, которые влияют на его работу. Вот короткий список:
количество попыток отправки пакета
таймаут между неудачными отправками пакета (возможно настроить «плавающий» таймаут — имеет смысл, чтобы несколько распределить нагрузку на сеть в определенный момент времени)
количество входящих и исходящий streams
количество попыток установления соединения
настройка автоматического закрытия
и так далее
Итоги наблюдений и заключение
Переработаем начальную информацию с учетом того, что мы выяснили в процессе:
протокол с установлением соединения
можно использовать как режимы «один‑к-одному», так и режимы «один‑ко‑многим»
доставка данных в общем случае по пакетам, но в большинстве случаев сообщениями
смешивание пакетов при частичной доставке имеет несколько режимов, которые влияют на устройство и обработку буферов
«надежная» доставка
возможность указать ppid при отправке — грубо говоря, указываем идентификатор используемого поверх sctp протокола
протокол специфичный и по библиотекам, все же, все не так хорошо
упорядоченность сообщений в рамках неких внутренних streams. Streams позволяют с одной стороны, обеспечить упорядочивание данных в рамках stream, с другой — распределить независимые данные между разными streams.
сообщения можно отправить неупорядоченным (но сообщение все равно отправляется в рамках stream)
ряд других преимуществ, которые в рамках введения все же не будут рассмотрены
Если обобщить — основная масса проблем связана с вопросом частичной доставки.
На этом у меня пока все, всем спасибо за внимание.