В этом материале даётся краткий обзор организации работы SCTP поверх UDP в ядре Linux.
Зачем нужна организация работы SCTP поверх UDP?
В вышеупомянутом RFC 6951 выделены две основные причины необходимости организации работы SCTP поверх UDP:
- Это нужно для того чтобы позволить SCTP-трафику проходить через устаревшие NAT-системы, не имеющие встроенной поддержки SCTP, как указано в этом и этом документах.
- Это нужно для того чтобы открыть возможность реализации SCTP на хостах, которые не предоставляют прямого доступа к уровню IP. В частности, речь идёт о том, чтобы у приложений была бы возможность использовать собственную реализацию SCTP в том случае, если операционная система не предоставляет им своей реализации SCTP.
Первая причина связана с решением «проблем промежуточных устройств». Эти проблемы доставили тем, кто пользуется SCTP, много неприятностей и встали на пути у широкого использования SCTP. В основе второй причины лежит потребность в наличии механизма, который позволяет разработчикам приложений пользовательского уровня создавать собственные реализации SCTP, основанные на UDP.
Как работает инкапсуляция SCTP-пакетов в UDP-пакеты?
Если в нашем распоряжении имеется возможность организации работы SCTP поверх UDP, то оказывается, что SCTP-пакеты инкапсулируются в UDP-пакеты. Реализация этого механизма создана с использованием набора API для работы с UDP-туннелями. Эти API уже использовались для организации работы протоколов VXLAN, GENEVE и TIPC.
Ядро, для того чтобы иметь возможность получать инкапсулированные пакеты, прослушивает особый UDP-порт на всех локальных интерфейсах. По умолчанию используется порт 9899
. Этот порт, кроме того, играет роль порта src
для UDP-пакетов, отправляемых хостом. Вполне логично то, что порт dest
системы, с которой ведётся обмен данными, должен быть портом, который прослушивает эта система. В качестве его номера тоже, по умолчанию, используется 9899
. Адреса портов src
и dest
, связанные с SCTP-механизмами, используются и в IP-заголовках:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IP(v6)-заголовок (адреса, связанные с SCTP) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| UDP-заголовок (src: 9899, dest: 9899) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Общий заголовок SCTP (SCTP-порты) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Блок SCTP #1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Блок SCTP #n |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Обратите внимание на то, что протокол SCTP принимает во внимание UDP-заголовок при нахождении точки фрагментации данных.
Как пользоваться реализацией SCTP поверх UDP?
С точки зрения использования описываемого здесь механизма в программах, можно сказать, что тут нет никакой разницы с тем, что было раньше. А именно, можно применять все стандартные возможности SCTP, в новых условиях доступны и все прежние API. Старые приложения будут работать правильно без необходимости внесения в них изменений или их перекомпиляции. Единственно — нужно правильно настроить UDP-порт (локальный порт, который прослушивает система, или порт src
) и точку инкапсуляции (порт, который прослушивает удалённая система, или порт dest
). Для конкретного сетевого пространства имён сделать это можно с помощью утилиты sysctl
:
# sysctl -w net.sctp.encap_port=9899
# sysctl -w net.sctp.udp_port=9899
Можно, в качестве альтернативы, использовать команду setsockopt
и настроить порт инкапсуляции для отдельного сокета (socket), для того, что в SCTP принято называть «ассоциацией» (association), или для транспортного механизма (transport):
setsockopt(SCTP_REMOTE_UDP_ENCAPS_PORT, port);
На стороне сервера обычно нет нужды специально настраивать порт инкапсуляции. Подробнее об этом мы поговорим в следующем разделе.
Порт инкапсуляции UDP
Концепция порта инкапсуляции UDP отличается большой гибкостью. На стороне отправителя данных глобальный порт инкапсуляции демонстрирует лишь значение, задаваемое по умолчанию:
- Порт инкапсуляции, заданный для отдельного сокета (per-socket) может быть использован тогда, когда другой сокет на одном хосте соединён с другим хостом, на котором используется другой UDP-порт.
- Порт инкапсуляции, заданный для отдельной ассоциации (per-association) может быть использован тогда, когда тот же самый сокет соединён с другим хостом, на котором используется другой UDP-порт.
- Порт инкапсуляции, задаваемый для отдельного транспортного механизма (per-transport), может быть использован тогда, когда в рамках одной и той же ассоциации нужно отправлять SCTP-пакеты, инкапсулированные в UDP-пакеты, пользуясь заданным транспортом.
На стороне получателя данных порт инкапсуляции обычно задавать не нужно:
- Порт инкапсуляции каждой ассоциации можно выяснить, проанализировав первый пакет
INIT
. Другие такие пакеты, в которых указан другой UDP-портsrc
, после этого будут отбрасываться. - Порт инкапсуляции для каждого транспортного механизма можно узнать из входящих пакетов соответствующего пути, номер порта может быть обновлён в любое время.
- Обычные SCTP-пакеты могут обрабатываться даже тогда, когда заданы порты инкапсуляции ассоциации и её транспортных механизмов.
Итоги
Если вы пользуетесь SCTP, если вам нравятся возможности этого протокола, вроде поддержки множественной адресации (Multihoming), многопоточности (Multi-streaming) и разрешения на потерю некоторых пакетов (Partial Reliability), но вы сталкиваетесь с «проблемами промежуточных устройств», теперь вы можете прибегнуть к возможностям ядра Linux, которые дают нам самый простой способ эти проблемы обойти.
Применяете ли вы SCTP?
Автор:
ru_vds