Пайка, сборка, проектирование чипов, чертежи на кульманах — все это было страстью моей жизни. И по сей день я остаюсь рядовым инженером на нижней ступеньке организационной структуры, потому что хочу быть именно там.
Стив Возняк, Debunks One of Apple's Biggest Myths, 2014
Как должно быть понятно из главы 7, операционная система помимо адреса, протокола и типа привязывает к сокету множество дополнительных параметров — опций. В главе 5 нам уже пришлось устанавливать некоторые опции сокета, например, для того, чтобы работать с ним в неблокирующем режиме.
В этой главе мы подробнее рассмотрим, какие бывают опции и как их устанавливать. Мы коснемся опций, общих как для Unix, так и для ОС Windows, а также специфичных для Unix-подобных систем, и составим достаточно подробный их список для Linux.
Также мы узнаем, как использовать опции сокетов для оптимизации работы сетевых соединений. Но более подробно эта тема будет раскрыта в книге 2.
Некоторые из установленных опций вызывают отправку управляющих сообщений, которые могут быть получены через вызов recvmsg(). Их мы подробнее рассмотрим при описании работы со вспомогательными данными.
Завершим главу рассмотрением функции fcntl(), предназначенной для работы с дескрипторами в Unix-подобных системах.
Данную главу можно использовать в качестве справочника при дальнейшей работе с книгой.
Функции объявлены в файле sys/socket.h или winsock.h в Windows.
Прототип функции для установки параметра:
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
И для его получения:
int getsockopt(int socket, int level, int option_name,
void *option_value, socklen_t *option_len);
Параметры функций setsockopt() и getsockopt():
• socket — дескриптор сокета.
• option_name — идентификатор параметра для установки или получения.
• level — уровень протокола:
• SOL_SOCKET — уровень библиотеки сокетов.
• Номер протокола для установки его опций. Может быть получен через функции getprotoent() и getprotobyname() либо задан константой, например IPPROTO_TCP. Также могут быть использованы SOL-опции: SOL_TCP, SOL_UDP и т.п., но этот вариант хуже поддерживается различными платформами.
• option_value — значение параметра. Тип не специфицирован. Он может быть любым, от int до произвольной структуры.
• option_len — длина параметра. Устанавливается как sizeof() значения параметра.
В случае успеха данные функции возвращают 0, в случае неудачи –1.
Эти функции работают в паре: зачастую, чтобы установить какие-либо флаги, не меняя того, что не требуется, необходимо получить текущие значения.
В различных ОС есть и другие функции. Например, в NetBSD есть функция getsockopt2():
int getsockopt2(int s, int level, int optname,
void * restrict optval, socklen_t * restrict optlen);
Она реализована через отдельный системный вызов и позволяет выбрать, какое значение нужно возвращать, задавая флаги в передаваемом буфере optval.
Но эта функция не является стандартной или даже широко используемой, поэтому рассчитывать на то, что она встретится где-либо еще, не стоит.
В Python данные функции представлены методами класса socket.socket:
@overload
def setsockopt(self, level: int, optname: int, value: int | bytes) -> None
@overload
def setsockopt(self, level: int, optname: int, value: None, optlen: int) -> None
@overload
def getsockopt(self, level: int, optname: int) -> int
@overload
def getsockopt(self, level: int, optname: int, buflen: int) -> bytes
Все параметры аналогичны параметрам C API, но Python-методы генерируют исключение в случае неудачи.
Поскольку чаще всего используются значения опций типа int, в Python для этого типа предусмотрена отдельная перегрузка socket.getsockopt() и socket.setsockopt().
Внимание! Если размер буфера не задан, метод socket.getsockopt() будет считать, что возвращаемое значение опции имеет тип int!
Пример использования функции для установки TTL IP-дейтаграмм:
extern "C"
{
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
}
...
int ttl_val = 255;
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Установить опции сокета на уровне IP: TTL.
if (setsockopt(sock, SOL_IP, IP_TTL, &ttl_val, sizeof(ttl_val)) != 0)
{
throw std::system_error(errno, std::system_category(),
"TTL setting failed");
}
Видим, что это опция IP. В функцию передается указатель на значение опции и ее размер. Напрямую передать константное значение опции нельзя.
Кроме того, опции могут быть разного типа, и передаваемое значение должно соответствовать тому, что ожидается для опции. Например, если ожидается тип int, не следует передавать значение типа char, даже если это может сработать.
С некоторыми опциями вы уже встречались в книге. Мы повторим их и составим их общий список. Это полезно для понимания того, какие параметры можно настроить и каким образом. Список будет неполный: в некоторых ОС могут быть свои опции, а какие-то могут отсутствовать, и даже в разных версиях одной и той же ОС состав опций может различаться. Поэтому для того, чтобы пользоваться большей частью опций, придется обращаться к документации.
Опции разбиты по уровням и в Unix-подобных системах описаны на разных страницах руководства, обычно в разделе man «Socket Options»:
• man 7 socket — уровень сокетов.
• man 7 ip — уровень IP. Также здесь описаны некоторые параметры raw-сокетов, они описаны от уровня IP и выше.
• man 7 ipv6 — уровень IPv6.
• man 7 tcp — уровень TCP.
• man 7 unix — сокеты Unix-domain.
Сказанное справедливо и для других протоколов.
Опции сокетов ОС Windows традиционно описаны в , где также можно посмотреть таблицу их поддержки в разных версиях ОС. Мы рассмотрим специфичные для ОС Windows опции в главе 18.
Некоторые параметры сокета устанавливаются не через опции, а через ioctl. Об этом подробнее см. в главе 10.
Для установки и получения опций данного уровня используется параметр level, имеющий значение SOL_SOCKET. Префикс для опций SO_ означает Socket Option.
Информационные опции, предназначенные только для чтения. Их нельзя установить через setsockopt(), только получить:
• SO_ACCEPTCONN — показывает, что сокет находится в режиме прослушивания или готовности принимать соединения, в который его переводит вызов listen(). Фактически опция просто возвращает значение флага, установленного вызовом listen().
• SO_TYPE — возвращает тип сокета, например SOCK_STREAM. В некоторых ОС может присутствовать аналогичная по смыслу опция SO_STYLE.
• SO_ERROR — получает и сбрасывает ошибку сокета.
Внимание! Значение ошибки может обновиться не сразу после того, как она возникла.
Опции маршрутизации:
• SO_BROADCAST — разрешить отправку широковещательных пакетов, если такая возможность поддерживается используемым протоколом. Позволяет установить или прочитать значение параметра.
• SO_DONTROUTE — устанавливает, должны ли исходящие данные отправляться на интерфейс, к которому привязан сокет, а не маршрутизироваться на какой-либо другой. То есть стек будет игнорировать таблицу маршрутизации.
Опция SO_DONTROUTE ведет себя по-разному в разных ОС. В ОС Windows установка будет успешной, но сокетный провайдер от Microsoft всегда использует таблицу маршрутизации для определения интерфейса, через который отправляются данные.
Тайм-ауты:
• SO_KEEPALIVE — проверять установленные соединения путем периодической передачи сообщений, если эта возможность поддерживается используемым протоколом. Опция — флаг типа bool либо int. Для TCP тайм-аут контролируется набором опций, которые описаны в разделе «Управление keep-alive» далее в этой главе.
• SO_RCVTIMEO — установить максимальный интервал, в течение которого функция приема ждет завершения. Если функция, принимающая данные, не завершается в течение указанного интервала, она возвращает либо частичный ответ, либо ошибку, если данные не были приняты. По умолчанию параметр равен 0, что означает отсутствие тайм-аута.
• SO_SNDTIMEO — параметр аналогичен предыдущему, но устанавливает тайм-аут на отправку данных.
Внимание! В ОС Windows, согласно MSDN, если блокирующая отправка или прием данных были прерваны по тайм-ауту, сокет будет находиться в неопределенном состоянии и должен быть закрыт.
В Linux тип параметра для опций SO_SNDTIMEO и SO_SNDTIMEO — структура struct timeval. В Windows — целое число миллисекунд.
Настройка отправки и приема данных:
• SO_LINGER — если при закрытии остались неотправленные данные и через эту опцию задан ненулевой тайм-аут, система будет пытаться отправить эти данные. Закрытие сокета завершится либо когда данные будут отправлены, либо когда истечет тайм-аут.
• SO_OOBINLINE — для протоколов с установлением соединения указывает, что внеполосные данные отправляются вместе с обычными.
• SO_REUSEADDR — разрешить повторное использование локальных адресов, если такая возможность поддерживается используемым протоколом. Присутствует в Linux, начиная с версии ядра 2.4, а также в ОС Windows и BSD-системах. Позволяет использовать один и тот же порт на разных IP-адресах в рамках одной системы везде, кроме BSD-систем.
• SO_REUSEPORT — делает то, что в BSD-системах делает SO_REUSEADDR: разрешает нескольким процессам слушать на одном и том же порту и адресе. Появилась, начиная с версии ядра Linux 3.9. О том, зачем это нужно и как понять, куда обратился клиент, мы расскажем в книге 2.
Опция SO_LINGER принимает как параметр :
struct linger
{
// Linger включен.
int l_onoff;
// Число секунд тайм-аута.
int l_linger;
};
В Linux структура определена в linux/filter.h, в ОС Windows — в файле winsock.h.
Опции управления буферами:
• SO_RCVBUF — установить размер буфера приема сокета.
• SO_SNDBUF — установить размер буфера отправки.
Размер задается в байтах.
Для отладки и трассировки:
• SO_BSDCOMPAT — режим совместимости с BSD «баг в баг». Может быть полезен для переносимости и поиска ошибок.
• SO_DEBUG — включить запись отладочной информации. Результат применения опции не стандартизован и зависит от конкретной ОС:
• В BSD при включении этого параметра ядро будет сохранять для TCP-сокетов в своем внутреннем кольцевом буфере подробную информацию обо всех пакетах. Посмотреть ее можно, используя программу trpt.
• В Linux, судя по коду ядра, не делает ничего.
• В ОС Windows поведение зависит от сокетного провайдера, но в провайдерах от Microsoft, используемых по умолчанию, не дает никакого эффекта.
Внимание! Некоторые из этих опций присутствуют и в ОС Windows, но там они не работают и представляют собой константы, не выполняющие действий.
Информационные, только для чтения:
• SO_DOMAIN — домен сокета, например AF_INET.
• SO_PROTOCOL — протокол, например IPPROTO_TCP.
• SO_PEERNAME — адрес удаленного узла, если подключен.
Привязка сокетов к сетевым интерфейсам:
• SO_BINDTODEVICE — получить имя связанного интерфейса, например eth0, или привязать интерфейс к сокету. Если передана пустая строка, интерфейс будет отвязан. Влияет как на исходящий, так и на входящий трафик. До Linux 3.8 была возможность только привязать или отвязать сокет, но не получить имя связанного устройства. Из-за этого данная опция гарантированно работает только для сокетов AF_INET, при возможности, например, когда у интерфейса есть IP-адрес, лучше использовать вызов bind(), что более переносимо. К тому же опция требует административных привилегий, чтобы переопределять настройки маршрутизации.
• SO_BINDTOIFINDEX — опция аналогична SO_BINDTODEVICE, но привязка выполняется по индексу. Это более надежный вариант, так как символьное имя сетевого интерфейса может измениться. Индекс — параметр типа int.
Оптимизации для работы с интенсивным трафиком:
• SO_CNX_ADVICE — connection advice, отчет о качестве соединения для ядра. Текущее поддерживаемое значение — 1. Оно говорит о том, что «маршрут плохой, требуется перенаправление». Ядро выполняет вызов dst_negative_advice, который попытается выбрать другой маршрут. Полезно для подключенных сокетов UDP и TCP, поскольку зачастую приложение оценивает качество связи в длительной перспективе лучше, чем ядро. Опция — флаг, который можно только сбросить или установить, но не прочитать. Тип — int.
• SO_INCOMING_CPU — установить или получить маску привязки сокета к CPU, где параметр — целое значение маски. Установить эту опцию можно, только начиная с версии ядра 4.
• SO_INCOMING_NAPI_ID — получить или установить уникальный идентификатор очереди приема. Дает возможность распределить обработку данных между разными потоками, которыми обрабатываются очереди. Каждый поток может работать с отдельным сетевым адаптером. Эта опция будет доступна, только если ядро Linux скомпилировано с CONFIG_NET_RX_BUSY_POLL.
• SO_PRIORITY — приоритет, зависящий от протокола. ОС будет в первую очередь обрабатывать на данном сокете пакеты с наивысшим приоритетом. Значения от 0 до 6 может установить пользователь. Для установки значений вне этого диапазона требуются привилегии CAP_NET_ADMIN или CAP_NET_RAW.
• SO_MAX_PACING_RATE — максимальная скорость отправки потока. По умолчанию не ограничена. Опция игнорируется, если она больше, чем значение, которое было задано через параметр maxrate утилиты tc. Подробнее см. в man 8 tc-fq.
• SO_TXTIME — задать режим дисциплины очередей Earliest TxTime First. Подробнее см. в man 8 tc-etf. Эта опция полезна для тестирования.
• SO_WIFI_STATUS — сообщает, был ли кадр подтвержден по Wi-Fi. Если опция активирована, будут поступать сообщения SCM_WIFI_STATUS, содержащие флаг, равный 0 или 1, где 1 означает, что кадр был подтвержден.
• SO_ZEROCOPY — предотвращает копирование больших буферов между пользовательским процессом и ядром, позволяя использовать обычные вызовы отправки данных через сокет без копирования.
Опция SO_ZEROCOPY временно разделяет буфер между процессом и сетевым стеком. После этого вместо копирования данных в буфер в произвольное время процесс должен ожидать уведомления, используя флаг MSG_ERRORQUEUE.
Подробнее об этом механизме будет рассказано в главе 23.
Опции расчета контрольных сумм:
• SO_NO_CHECK — отключить расчет контрольных сумм дейтаграмм. Влияет на различные протоколы, такие как UDP, iSCSI или DDP, в стеке AppleTalk. Для UDP отключает расчет контрольных сумм отправляемых дейтаграмм. Достаточно старая недокументированная опция, которую ранее не имело смысла использовать: в UDP расчет тратит очень мало ресурсов. Впоследствии действие опции расширилось на другие протоколы, и ее применение редко, но может быть оправданно. Например, в туннелях, где не требуется рассчитывать контрольную сумму внутреннего пакета.
• SO_TXREHASH — управляет пересчетом хеша для каждого сокета. По умолчанию сокеты отключают повторное хеширование при инициализации и используют значение из sysctl в состоянии прослушивания. При включении этого флага, если anycast-соединение, при котором данные отправляются ближайшему узлу из группы, завершается с ошибкой, система изменяет метку потока IPv6 и повторяет соединение, которое теперь может быть успешным, так как путь потока изменится. Опция появилась только в ядре версии 6.3.
• SO_NOFCS — отключить расчет FCS адаптером Ethernet. FCS или Frame Check Sequence — значение, добавляемое в конец кадра для обнаружения ошибок передачи принимающим абонентом, как показано на рис. 8.1. Обычно это CRC кадра. Если опция включена, вместо FCS будут использоваться последние четыре байта пакета, отправленного из пользовательского пространства.
Рис. 8.1. Поле FCS в Ethernet-кадре
Опции безопасности сокетов Unix-domain, некоторые из данных опций будут рассмотрены далее в этой главе, в разделе «Опции сокетов Unix-domain»:
• SO_PASSCRED — включить или выключить получение сообщения SCM_CREDENTIALS.
• SO_PASSPIDFD — включить или выключить получение сообщения SCM_PIDFD.
• SO_PASSEC — включить или выключить получение сообщения SCM_SECURITY.
• SO_PEERCRED — получить учетные данные однорангового процесса.
• SO_PEERPIDFD — аналог SO_PEERCRED, который включает или выключает получение сообщения SCM_PIDFD.
Управление буферами и памятью сокета:
• SO_PEEK_OFF — получить данные из очереди приема без их удаления. Поддерживается только для сокетов AF_UNIX.
• SO_RCVLOWAT — минимальное количество байтов в буфере приема, по достижении которого данные будут записаны в буфер пользователя. Опция существует и в ОС Windows, но она не поддерживается и нужна для обратной совместимости со старым BSD-стеком.
• SO_SNDLOWAT — минимальное количество байтов в буфере передачи, по достижении которого данные будут переданы на уровень протокола для отправки их в сеть. Опция всегда возвращает 1 и не может быть установлена. В ОС Windows работает так же, как предыдущая опция.
• SO_SNDBUFFORCE — установить размер буфера отправки, не обращая внимания на заданное ограничение размера. То же самое, что и SO_SNDBUF, но будет работать, только если процесс имеет административные привилегии CAP_NET_ADMIN.
• SO_RCVBUFFORCE — установить размер буфера приема, не обращая внимания на заданное ограничение размера. Требует привилегии CAP_NET_ADMIN.
• SO_RXQ_OVFL — количество пакетов, отброшенных с момента создания сокета. В некоторых системах это может быть количество пакетов, отброшенных с момента последнего сообщения, принятого сокетом. При включении поступит сообщение SO_RXQ_OVFL, содержащее 32-битное значение без знака.
• SO_BUF_LOCK — переключить автоматическую установку размера буферов. Принимает маску, в которой первый бит — флаг, отвечающий за буфер отправки, а второй — за буфер приема. Используется, например, проектом , реализующим точки восстановления системы и миграцию ОС. Если после восстановления установить заданный размер буфера, автоматическая подстройка размера будет выключена. С помощью данной опции ее можно снова включить.
• SO_MEMINFO — позволяет читать SK_MEMINFO_VARS, то есть получить всю информацию, связанную с памятью, используя системный вызов.
• SO_RESERVE_MEM — зарезервировать память для сокета. Если опция установлена, сетевой стек будет тратить меньше циклов на выполнение распределения и освобождения памяти, что должно привести к улучшению производительности. Работать опция будет, только если включены контрольные группы памяти, необходимые для учета выделенной пользователю памяти.
Опция SO_SNDLOWAT не работает и нужна только для совместимости.
С опцией SO_RCVLOWAT есть сложности: ее не учитывают асинхронные механизмы select(), poll(), epoll. Они вернут управление, когда появится хотя бы один байт в буфере. Но если опция SO_RCVLOWAT установлена, выполнение потока блокируется, пока в буфер не будет получено заданное через эту опцию количество байтов.
Опция SO_MEMINFO вернет массив, который содержит значения по индексам:
• SK_MEMINFO_RMEM_ALLOC — объем данных в очереди приема.
• SK_MEMINFO_RCVBUF — размер буфера приема, заданный через SO_RCVBUF.
• SK_MEMINFO_WMEM_ALLOC — объем данных в очереди передачи.
• SK_MEMINFO_SNDBUF — размер буфера передачи, заданный через SO_SNDBUF.
• SK_MEMINFO_FWD_ALLOC — объем памяти, выделенный TCP для будущего использования.
• SK_MEMINFO_WMEM_QUEUED — объем данных, поставленных в очередь TCP, но еще не отправленных.
• SK_MEMINFO_OPTMEM — объем памяти, выделенный для служебных потребностей сокета, например для фильтров.
• SK_MEMINFO_BACKLOG — количество необработанных пакетов.
• SK_MEMINFO_DROPS — количество отброшенных пакетов.
Генерация меток времени:
• SO_TIMESTAMP — включить или отключить метку времени дейтаграмм. В сокет будут приходить управляющие сообщения SCM_TIMESTAMP, если опция включена.
• SO_TIMESTAMPNS — включить или отключить получение управляющего сообщения SCM_TIMESTAMPNS. Для получения метки используются часы реального времени. Конфликтует с SO_TIMESTAMP: одновременно можно использовать только одну из опций.
• SO_TIMESTAMPING — управляет генерацией временных меток. Значение — комбинация флагов:
• SOF_TIMESTAMPING_RX_HARDWARE — запросить временные метки принятых кадров от сетевого адаптера.
• SOF_TIMESTAMPING_RX_SOFTWARE — временные метки принятых данных, которые поступают в ядро. Метки времени генерируются сразу после того, как драйвер устройства передает пакет ядру.
• SOF_TIMESTAMPING_TX_HARDWARE — временные метки передаваемых кадров, созданные сетевым адаптером.
• SOF_TIMESTAMPING_TX_SOFTWARE — временные метки передаваемых данных, покидающих ядро. Эти метки времени генерируются в драйвере устройства до передачи пакета на сетевой интерфейс. Могут быть доступны не для всех устройств.
• SOF_TIMESTAMPING_TX_SCHED — временные метки передаваемых данных перед входом в планировщик пакетов. Вместе с предыдущим флагом позволяет точно измерить задержку в очереди.
• SOF_TIMESTAMPING_TX_ACK — временные метки всех подтвержденных данных в буфере отправки. Имеет смысл для надежных протоколов и реализован только для TCP.
• SOF_TIMESTAMPING_SOFTWARE — вернуть программные временные метки, если они доступны TX+RX.
• SOF_TIMESTAMPING_RAW_HARDWARE — вернуть аппаратные временные метки, созданные SOF_TIMESTAMPING_TX_HARDWARE, если они доступны.
В редких случаях в Linux могут встретиться следующие опции:
• SO_TIMESTAMPING_OLD, SO_TIMESTAMPING_NEW.
• SO_RCVTIMEO_OLD, SO_RCVTIMEO_NEW.
• SO_SNDTIMEO_OLD, SO_SNDTIMEO_NEW.
• SO_TIMESTAMPNS_OLD SO_TIMESTAMPNS_NEW.
Это макросы, которые реализуют соответствующие опции без суффиксов. Выбираются на основе размерности типа long и нужны, чтобы правильно выбрать тип счетчика времени. Явно ими пользоваться не следует, они нужны только для совместимости разных версий ABI.
Опции BPF:
• SO_ATTACH_FILTER, SO_DETACH_FILTER — добавить к сокету фильтр классического BPF или убрать фильтр.
• SO_ATTACH_BPF, SO_DETACH_BPF — добавить к сокету фильтр Extended BPF или убрать фильтр.
• SO_GET_FILTER — то же, что и SO_ATTACH_FILTER.
• SO_ATTACH_REUSEPORT_CBPF, SO_ATTACH_REUSEPORT_EBPF — вместе с опцией SO_REUSEPORT позволяет использовать классический или расширенный BPF, чтобы задать, как пакеты будут распределяться между сокетами, на которых установлен SO_REUSEPORT и которые имеют одинаковый локальный адрес. Программа BPF должна вернуть индекс сокета от 0 до N – 1, в противном случае выбирать сокет будет простой механизм SO_REUSEPORT. Сокеты нумеруются в порядке их добавления в группу.
• SO_LOCK_FILTER — установка этой опции запрещает менять фильтры, связанные с сокетом. Для изменения требуется привилегия CAP_NET_RAW.
• SO_ATTACH_REUSEPORT_CBPF, SO_ATTACH_REUSEPORT_EBPF — позволяет изменить политику работы опции SO_REUSEPORT для заданной пользователем BPF программы.
• SO_BPF_EXTENSIONS — вернуть расширения BPF. Опция только для чтения. Тип возвращаемого значения — int.
Опции SO_DETACH_FILTER и SO_DETACH_BPF одинаковы, то есть могут быть использованы для удаления фильтров BPF любого типа.
Функция, возвращающая расширения BPF в ядре, выглядит так:
static inline int bpf_tell_extensions(void)
{
return SKF_AD_MAX;
}
То есть это сейчас просто комбинация идентификаторов расширений.
Управление работой опроса занятости:
• SO_BUSY_POLL — устанавливает приблизительное время в микросекундах для опроса занятости при отсутствии данных в блокирующем режиме. Также влияет на время опроса функциями select() и poll(). Значение по умолчанию для этого параметра содержится в /proc/sys/net/core/busy_read. Для увеличения значения требуется привилегия CAP_NET_ADMIN.
• SO_PREFER_BUSY_POLL — включить режим Preferred Busy Polling, что может увеличить производительность для сокетов с большим трафиком и значительно улучшить производительность на одном ядре за счет того, что прерывания NAPI завершаются раньше, а опрос занятости возобновляется по сторожевому таймеру. Работает не только для обычных сокетов, но и для DPDK-драйвера семейства AF_XDP. DPDK будет рассмотрен в главе 23. Опция появилась в ядре версии 5.11.
• SO_BUSY_POLL_BUDGET — установить бюджет NAPI для каждого сокета опроса. По умолчанию — 8 микросекунд.
• SO_SELECT_ERR_QUEUE — если опция установлена, состояние ошибки в сокете вызывает уведомление не только через набор исключений функции select(), но и через poll(), который вернет POLLPRI, если наступит событие POLLERR. С Linux 4.16 использование этой опции для получения уведомлений больше не требуется. Опция сохранена для обратной совместимости. Подробнее о ней см. в man 7 socket.
Опции доступны, только когда ядро Linux скомпилировано с поддержкой опроса, то есть с параметром CONFIG_NET_RX_BUSY_POLL.
Busy polling, или опрос занятости, — способность пользовательского потока активно ожидать входящих сетевых сообщений, используя прямой опрос кольцевого буфера устройства, вместо традиционного способа ожидания, при котором заблокированный поток разблокируется прерыванием.
То есть приложение загружает процессор в ожидании пакетов для уменьшения задержки и повышения производительности.
Внимание! Опрос занятости может уменьшить задержку для некоторых приложений, но при его использовании требуется соблюдать осторожность, поскольку оно приводит к увеличению загрузки процессора и повышенному энергопотреблению.
Значение в /proc/sys/net/core/busy_poll определяет, как долго select() и poll() будут выполнять опрос занятости, когда они работают с сокетами с установленным SO_BUSY_POLL и нет событий, о которых нужно сообщить.
В обоих случаях опрос занятости будет выполняться только тогда, когда сокет в последний раз получил данные от сетевого устройства, поддерживающего эту опцию.
Режим Preferred Busy Polling нужен из-за того, что некоторые асинхронные приложения рассчитаны на то, что большая часть обработки NAPI выполняется путем опроса занятости, а перегруженный NAPI контекст никогда не будет разрешать опрос занятости. В случае же использования этого режима обработка softirq может быть прервана сторожевым таймером и запущен опрос.
Прочие опции:
• SO_MARK — позволяет устанавливать на каждый пакет метку. Эта опция типа uint32 устанавливает значение поля в объекте ядра Linux. В результате появляется возможность маршрутизировать сообщения этого сокета по метке.
• SO_RCVMARK — получать в управляющих сообщениях метку, установленную администратором. Если опция установлена и метка задана, будет приходить сообщение SO_MARK. Требует привилегий CAP_NET_RAW или CAP_NET_ADMIN.
• SO_COOKIE — возвращает куки сокета, то есть непрозрачный уникальный идентификатор. Тип значения — uint64.
• SO_NETNS_COOKIE — возвращает куки сетевого пространства имен. Требуется, чтобы различать пространства имен, которым принадлежат сокеты. Тип опции — uint64.
• SO_SECURITY_AUTHENTICATION, SO_SECURITY_ENCRYPTION_TRANSPORT, SO_SECURITY_ENCRYPTION_NETWORK — уровни безопасности IPv6. Работа с ними пока не реализована.
Параметр SO_MARK может использоваться для работы межсетевого экрана с указанными сокетами. В Linux это IPTables.
Например, следующий вызов IPTables использует метку, чтобы ограничивать поток и журналировать пакеты с заданным префиксом:
iptables -A INPUT -m mark ! --mark 0 -m limit --limit 8/min -j LOG --log-prefix "Socket-Mark: ".
Для установки опций требуется указать level, равный IPPROTO_IP или SOL_IP.
В Unix-подобных ОС для C++ необходимо включить заголовочный файл netinet/ip.h.
В книге мы не будем рассматривать многоадресную передачу, но приведем опции, которые подробнее описаны в man:
• IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP — используя данные опции, можно управлять присутствием сокета в multicast-группах, добавлять и удалять его.
• IP_BLOCK_SOURCE, IP_UNBLOCK_SOURCE — управление получением данных от конкретных источников.
• IP_MULTICAST_ALL — изменение политики многоадресной передачи. Если опция включена, сокет будет получать сообщения для всех групп, в которых состоит данный узел. Иначе только для явно указанных через IP_ADD_MEMBERSHIP.
• IP_MULTICAST_IF — привязать сокет многоадресной рассылки к интерфейсу.
• IP_MULTICAST_LOOP — если опция включена, многоадресные пакеты будут обратно отправляться в локальные сокеты.
• IP_MULTICAST_TTL — установить или прочитать TTL для многоадресных пакетов.
• IP_MSFILTER — изменить фильтр многоадресных пакетов.
Влияют на то, как работают функции bind() и connect():
• IP_BIND_ADDRESS_NO_PORT — информирует ядро не резервировать эфемерный порт, когда вызывается bind(). Порт будет выбран на этапе вызова connect(). При использовании опции в клиенте вызывается bind() с указанием source IP и портом 0. Опция уменьшает вероятность исчерпания пространства эфемерных портов. Используется, например, в Nginx или внутренними приложениями сервиса Cloudflare. Опция доступна в Linux с версии 4.2.
• IP_FREEBIND — флаг, разрешающий привязку к нелокальному или несуществующему IP-адресу. Включение опции позволяет ожидать подключения на сокете, для работы которого нет сетевого интерфейса. Опция может быть использована, например, в балансировщиках нагрузки, перенаправляющих обратный трафик на разные интерфейсы.
• IP_PORTSEL — эта опция присутствует в NetBSD, в Linux ее нет. С ее помощью можно указать алгоритм, по которому будет производиться автоматический выбор портов, например, как будут выбираться эфемерные порты: bsd, random_start, random_pick, hash, doublehash, randinc. Подробнее см. RFC 6056 «Recommendations for Transport-Protocol Port Randomization».
• IP_LOCAL_PORT_RANGE — задать диапазон выбора локального порта. Эфемерные порты выбираются из этого диапазона. Значение — uint32, первые 16 бит которого содержат нижнюю границу диапазона, вторые 16 бит — верхнюю. Диапазон по умолчанию настраивается в /proc/sys/net/ipv4/ip_local_port_range.
• IP_TRANSPARENT — включить прозрачное проксирование на этом сокете. Опция присутствует в Linux и дает возможность:
• привязываться к адресам, не являющимся локальными;
• получать соединения и пакеты от сеансов TPROXY, перенаправленных iptables.
• IP_UNICAST_IF — установить индекс интерфейса исходящих дейтаграмм. Влияет только на исходящий трафик и не требует административных привилегий для установки. Для сокетов, ориентированных на соединение, опция игнорируется. Тип значения — int. Опция была добавлена для проекта Wine.
Опция IP_TRANSPARENT обычно мало полезна, но мы рассмотрим некоторые ее возможные применения подробнее в главе 21.
Определение MTU нужно для того, чтобы понимать, данные какого размера можно передавать единовременно и, соответственно, каким должен быть размер пользовательских буферов:
• IP_MTU — получить текущее значение MTU. Опция только для чтения на сокете, который уже подключен. Реальное значение MTU может быть установлено пользователем, но скорее всего, оно будет получено через определение MTU пути.
• IP_MTU_DISCOVER — управление Path MTU Discovery или определением MTU на пути следования пакета. Также опция управляет флагом DF в IP-пакете. Опция принимает несколько значений:
• IP_PMTUDISC_WANT — использовать настройки для маршрута.
• IP_PMTUDISC_DONT — не выполнять определение MTU пути.
• IP_PMTUDISC_DO — всегда определять MTU пути.
• IP_PMTUDISC_PROBE — установить флаг DF — Don't Fragment, но игнорировать MTU пути.
• IP_NODEFRAG — если опция установлена, сетевой уровень не будет пересобирать исходящие пакеты. Опция корректна только для raw-сокетов.
• IP_RECVFRAGSIZE — установить или получить размер самого большого фрагмента пакета. Будет использовано для определения Path MTU. При чтении опция возвращается, только когда данные были фрагментированы.
Path MTU discovery используют достаточно редко, предпочитая использовать жестко заданный «безопасный» размер пакета. Однако правильнее установить размер из MTU пути. В новых приложениях по возможности его следует использовать.
Используя следующие константы, можно получить опции заголовка IP:
• IP_OPTIONS — установить или получить опции IP, которые будут отправляться с каждым пакетом из этого сокета в сообщении IP_OPTIONS. Значение — указатель на буфер памяти. Допустимые опции можно посмотреть в RFC 791 «Internet Protocol». Если они не заданы, поведение будет зависеть от вышележащего типа протокола. Например, для SOCK_STREAM первый входящий пакет установит значения опций, которые будут добавляться ко всем отправляемым пакетам. Вызов getsockopt() вернет опции IP, которые используются для передачи.
• IP_RECVOPTS — отправить все опции из входящих IP-дейтаграмм пользователю в управляющем сообщении IP_OPTIONS. Не поддерживается для сокетов SOCK_STREAM.
• IP_RETOPTS — опция идентична IP_RECVOPTS, но возвращает необработанные опции с отметкой времени и незаполненными параметрами записи маршрута. Используется для совместимости с системами BSD.
Для получения других опций используются константы:
• IP_PASSSEC, SO_PEERSEC — данные опции требуются при работе с IPSec, где отвечают за передачу контекста безопасности.
• IP_RECVTOS — если опция включена, сообщение IP_TOS передается с входящими пакетами в сообщении IP_TOS. Он содержит байт, определяющий поле типа обслуживания/приоритета в заголовке IP-пакета.
• IP_MINTTL — установить минимальное время TTL. Все пакеты с более низким TTL будут отброшены. Данная опция может использоваться для того, чтобы отбросить пакеты от внешних узлов, которые прошли в локальную сеть. Эта опция есть и во FreeBSD.
• IP_RECVTTL — если эта опция включена, можно получить управляющее сообщение IP_TTL с 32-битным полем времени жизни полученного пакета. Не поддерживается для сокетов SOCK_STREAM.
• IP_TTL — установить или получить TTL, которое используется для каждой дейтаграммы, отправляемой из этого сокета.
• IP_TOS — установить или получить значение поля Type-Of-Service для каждой IP-дейтаграммы, отправляемой из этого сокета. Используется для определения приоритетов пакетов в сети. Возможен один из следующих вариантов TOS:
• IPTOS_LOWDELAY — минимизировать задержки интерактивного трафика. В Linux по умолчанию устанавливается данный TOS.
• IPTOS_THROUGHPUT — оптимизировать пропускную способность.
• IPTOS_RELIABILITY — оптимизировать надежность.
• IPTOS_MINCOST — оптимизировать цену передачи. Следует использовать для «заполняющих данных», когда скорость передачи не имеет значения.
TOS имеет размер в октет, то есть может принимать 256 значений. Обычно можно указывать только одно из описанных выше значений, хотя точная их обработка зависит от дисциплины очередей.
Внимание! В ОС Windows, начиная с XP, опция IP_TOS устарела и работать не будет.
Поле ToS в большинстве реализаций протокола IP равно 0 и не используется по назначению. Некоторые его биты могут быть использованы для ECN — явного уведомления о перегрузках.
Но изначальное предназначение — маршрутизация по типу обслуживания. Сейчас она является устаревшей частью IP.
Про обработку ошибок и отладку мы будем говорить подробнее в главах 24 и 25. Здесь приведем опции, которые для этого могут быть полезны:
• IP_RECVERR — включить расширенную передачу сообщений об ошибках. Если эта функция включена на дейтаграммных сокетах, все сгенерированные ошибки будут помещены в очередь. Получить ошибку можно через вызов функции recvmsg() с флагом MSG_ERRQUEUE. Подробнее данная опция рассмотрена в главе 25.
• IP_RECVERR_RFC4884 — вернуть смещение начала структуры расширения для сообщений ICMP, если она присутствует. Расширения описаны в RFC 4884 «Extended ICMP to Support Multi-Part Messages».
• IP_PKTINFO — вызывает отправку служебного сообщения, содержащего информацию с адресами и связанным интерфейсом. Применима только для дейтаграммных сокетов. Тип значения — int, который содержит флаг, равный 0 или 1.
• IP_PKTOPTIONS — получать дополнительные опции пакета с дейтаграммами в сообщении IP_PKTINFO. Принимает указатель на буфер, как и IP_OPTIONS. Для приема опций размер этого буфера необходимо увеличить на 1024.
• IP_RECVORIGDSTADDR — включить отправку сообщения IP_ORIGDSTADDR, в котором ядро возвращает исходный адрес получателя пришедшей дейтаграммы. После включения адрес может быть получен через вызов функции recvmsg(). Эта опция возвращает оригинальный адрес назначения. Используется для получения адреса, например, вместе с опцией IP_TRANSPARENT, когда адрес назначения не совпадает с адресом локального узла.
• IP_CHECKSUM — если опция установлена, после завершения расчета контрольной суммы IP-пакета будет отправлено управляющее сообщение, содержащее ее значение.
Структура для IP_PKTINFO:
struct in_pktinfo
{
// Индекс интерфейса.
unsigned int ipi_ifindex;
// Локальный адрес.
in_addr ipi_spec_dst;
// Адрес назначения из заголовка.
in_addr ipi_addr;
};
Как уже говорилось, raw-сокеты работают поверх IP, поэтому управляются на уровне SOL_IP:
• IP_HDRINCL — опция, которая уже известна по работе с raw-сокетами и которая может быть использована только с ними. IP-заголовок пакета уже добавлен пользователем, что будет учтено стеком при отправке пакета.
• IP_ROUTER_ALERT — предоставляет механизм, с помощью которого маршрутизаторы перехватывают пакеты, не адресованные непосредственно им, без значительного падения производительности. Имеет тип int. Положительное число указывает значение опции заголовка Router Alert, которое будет индикатором перехвата, отрицательное — выключает перехват. Опция применима только к raw-сокетам.
Перехваченные пакеты не пересылаются ядром, повторно их отправить должен пользователь. Это полезно, например, для демонов RSVP в пользовательском пространстве или для работы с IGMP.
Помимо Linux, данные опции работают в NetBSD:
• IP_IPSEC_POLICY — установить политику IPSec.
• IP_XFRM_POLICY — настроить политику IP XFRM. Подробнее см. в man 8 ip-xfrm.
Опции требуют привилегии CAP_NET_ADMIN.
В NetBSD эта опция может быть использована следующим образом:
// Строка, задающая политику.
const char *policy = "in ipsec ah/transport//require";
// Буфер, в который "скомпилирована" политика.
char *buf = ipsec_set_policy(policy, strlen(policy));
setsockopt(sock, IPPROTO_IP, IP_IPSEC_POLICY, buf, ipsec_get_policylen(buf));
В Linux обе эти опции имеют одинаковое значение и отвечают за настройку политики IP XFRM. Если их значение нулевое, политика будет сброшена.
Внимание! Далеко не все приведенные опции IP поддерживаются всеми ОС. В некоторых ОС существуют опции, которые не вошли в список выше. Чтобы узнать, какие опции поддерживает ОС, на которой будет компилироваться и работать ваше приложение, обратитесь к документации.
Для установки опций требуется указать level, равный IPPROTO_IPV6. Многие опции повторяют опции для IP.
В Unix-подобных ОС для C++ необходимо включить заголовочный файл netinet/ip6.h. Для некоторых Linux-специфичных опций требуется включить файл linux/in6.h.
Идентификаторы, которые содержат номер RFC, например IPV6_2292RTHDR, представляют собой устаревшие аналоги идентификаторов с тем же именем, но без номера.
Кроме того, в RFC 2292 «Advanced Sockets API for IPv6» для поля Next Header могут быть указаны названия констант, которые отличаются от названий в системе.
Опции для работы с многоадресными группами в целом аналогичны опциям IPv4 и отличаются лишь префиксом IP6: IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_MULTICAST_LOOP, IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF.
Опции похожи на опции для IPv4:
• IPV6_PORTSEL — то же, что IP_PORTSEL, но для IPv6. Используется только для NetBSD.
• IPV6_TRANSPARENT — аналогична IP_TRANSPARENT.
• IPV6_FREEBIND — аналогична IP_FREEBIND.
Кроме опции, возвращающей значение Path MTU, остальные повторяют опции IPv4:
• IPV6_MTU — получить или установить MTU для сокета. Если включено обнаружение MTU, устанавливаемый MTU ограничивается MTU устройства или MTU пути.
• IPV6_MTU_DISCOVER — управление обнаружением path-MTU на сокете. Опция аналогична опции для IPv4.
• IPV6_RECVPATHMTU — включить получение управляющего сообщения IPV6_PATHMTU, содержащего найденный MTU пути.
• IPV6_DONTFRAG — не фрагментировать пакеты. Опция — флаг типа int. Аналога для IPv4 в Linux нет, хотя она есть в BSD-системах. В Linux IPv4 используйте для той же цели опцию IP_MTU_DISCOVER со значением IP_PMTUDISC_DO.
Опции позволяют включить доставку управляющих сообщений для входящих дейтаграмм, содержащих заголовки расширения из полученного пакета. Поддерживаются эти параметры только сокетами SOCK_DGRAM и SOCK_RAW.
Их также можно установить для исходящих пакетов, используя вызов sendmsg(), описанный далее:
• IPV6_RECVRTHDR — включить получение сообщения IPV6_RTHDR, содержащего заголовок маршрутизации.
• IPV6_AUTHHDR — включить получение сообщения IPV6_AUTHHDR, содержащего заголовок аутентификации.
• IPV6_RECVDSTOPTS — включить получение сообщения IPV6_DSTOPTS, содержащего параметры назначения.
• IPV6_RECVHOPOPTS — включить получение сообщения IPV6_HOPOPTS, содержащего параметры Hop-by-Hop: заголовок Hop-by-Hop options.
• IPV6_RECVHOPLIMIT — включить получение сообщения IPV6_HOPLIMIT, содержащего число переходов пакета.
• IPV6_RECVORIGDSTADDR — аналог IP_RECVORIGSTDADDR.
• IPV6_RECVFRAGSIZE — аналог IP_RECVFRAGSIZE.
• IPV6_RECVTCLASS — включить получение управляющего сообщения IPV6_TCLASS, содержащего класс трафика пакета.
Тип этих опций — bool. Управляющие сообщения имеют тот же тип, что и опция заголовка.
Несколько опций для управления потоками данных IPv6:
• IPV6_AUTOFLOWLABEL — автоматически генерировать метку потока на основе хеша потока. Позволяет промежуточным устройствам, таким как маршрутизаторы, идентифицировать потоки пакетов для таких механизмов, как многопутевая маршрутизация. Флаг типа int.
• IPV6_FLOWINFO_SEND — флаг типа int, при включении которого отправляется сообщение IPV6_FLOWINFO, содержащее метку потока типа uint32.
• IPV6_FLOWLABEL_MGR — запросить диспетчер меток потока для выделения новой метки потока, повторного использования уже выделенной метки или удаления старой метки потока. Тип опции — структура in6_flowlabel_req.
Структура для опции IPV6_FLOWLABEL_MGR:
struct in6_flowlabel_req
{
// Адрес назначения IPv6, связанный с меткой.
struct in6_addr flr_dst;
// Значение метки потока в сетевом порядке байтов. 0 — случайная метка.
__u32 flr_label;
// Запрошенная операция: IPV6_FL_A_GET, IPV6_FL_A_PUT, IPV6_FL_A_RENEW.
__u8 flr_action;
// Кому разрешено повторно использовать одну и ту же метку потока:
// IPV6_FL_S_NONE, IPV6_FL_S_EXCL — частная метка,
// IPV6_FL_S_PROCESS — процессу, IPV6_FL_S_USER — пользователю,
// IPV6_FL_S_ANY — всем.
__u8 flr_share;
// Модификаторы:
// IPV6_FL_F_CREATE — создать новую метку,
// IPV6_FL_F_EXCL — не создавать новую метку.
__u16 flr_flags;
// Минимальное число секунд, которое метка существует.
__u16 flr_expires;
// После того как метка удалена, она не будет использована повторно
// минимум заданное число секунд.
__u16 flr_linger;
__u32 __flr_reserved;
// Далее могут идти опции в формате IPV6_PKTOPTIONS: IPV6_HOPOPTS,
// IPV6_RTHDR, IPV6_DSTOPTS.
};
Опции специфичны для Linux.
В главе 2 мы упоминали двойной IP-стек и работу с несколькими версиями IP одновременно. Следующие опции позволяют настроить данные режимы:
• IPV6_ADDRFORM — конвертировать сокет AF_INET6 в сокет другого семейства адресов. Поддерживается только AF_INET и разрешена только для сокетов IPv6, которые подключены и привязаны к адресу IPv4, отображенному на IPv6. Опция может быть использована для передачи файлового дескриптора сокета программам, которые не знают, как работать с API IPv6.
• IPV6_V6ONLY — включить для сокета работу только поверх IPv6. В этом случае пространства портов протоколов, работающих поверх IPv4 и IPv6, не будут пересекаться. Необходима для того, чтобы приложения IPv4 могли привязываться к тому же порту, что и приложения IPv6. Если опция установлена в 0, сокет можно использовать для отправки и получения пакетов на адрес IPv6 либо адрес IPv6, сопоставленный с IPv4.
• IPV6_UNICAST_HOPS — лимит одноадресных переходов для сокета: –1 — использование маршрута по умолчанию, в противном случае от 0 до 255.
• IPV6_MINHOPCOUNT — поле заголовка минимального количества переходов для входящих пакетов, полученных через сокеты TCP и UDP.
Данные опции практически аналогичны опциям IPv4:
• IPV6_RECVERR — управление расширенной передачей сообщений ошибок. Опция аналогична такой же для IPv4.
• IPV6_RECVPKTINFO — включить доставку управляющего сообщения IPV6_PKTINFO для входящих дейтаграмм. Аналог IP_PKTINFO для IPv6. Сообщения представлены структурой in6_pktinfo. Поддерживается только сокетами типа SOCK_DGRAM и SOCK_RAW. Тип значения — int, содержащий флаг, равный 0 или 1.
• IPV6_2292PKTINFO — устаревшая опция, аналогичная IPV6_RECVPKTINFO.
• IPV6_PKTOPTIONS — аналог IP_PKTOPTIONS.
• IPV6_RECVERR_RFC4884 — аналог IP_RECVERR_RFC4884.
Для raw-сокетов доступна опция IPV6_ROUTER_ALERT, аналогичная опции IP_ROUTER_ALERT для IPv4.
Опцию IP_HDRINCL можно использовать на raw-сокетах IPv6 без изменения.
Внимание! Для IPv6 определен свой набор опций. Однако на IPv6-сокет можно установить некоторые опции IP-протокола, если указать для них уровень IPPROTO_IP. Важно следить, чтобы опции с префиксом IPV6_ всегда устанавливались для уровня IPPROTO_IPV6, а не IPPROTO_IP.
Для установки опций требуется указать level, равный IPPROTO_UDP или SOL_UDP.
В Unix-подобных ОС для C++ нужно включить заголовочный файл netinet/udp.h.
Несколько применимых опций:
• UDP_CORK — если эта опция включена, данные, отправляемые через сокет, объединяются в одну дейтаграмму, которая будет отправлена после выключения опции. Пример использования этой опции показан в книге 2, в главах о высокопроизводительных приложениях. Опция не кросс-платформенная, в ОС Windows есть ее аналог, рассмотренный в главе 18. В Linux доступна с версии 2.5.44.
• UDP_ENCAP — сокет будет принимать инкапсулированные пакеты. Опция принимает следующие значения:
• 0 — выключена.
• UDP_ENCAP_ESPINUDP_NON_IKE — не используется. Требовалось для прохождения NAT для IPSec, описанного в документе «».
• UDP_ENCAP_ESPINUDP — инкапсуляция IPSec-пакетов в UDP. Описана в «».
• UDP_ENCAP_L2TPINUDP — инкапсуляция пакетов L2TP. Описана в RFC 2661 «Layer Two Tunneling Protocol L2TP». То есть UDP используется как транспорт для VPN.
• UDP_ENCAP_GTP0 — используется GPRS Tunnelling Protocol. В UDP инкапсулированы пакеты GSM TS 09.60, то есть GPRS для 2G.
• UDP_ENCAP_GTP1U — используется GPRS Tunnelling Protocol. В UDP инкапсулированы пакеты 3GPP TS 29.060. Это GPRS для 3G.
• UDP_ENCAP_RXRPC — UDP используется как транспорт для протокола . Требуется для безопасного вызова удаленных процедур сервера клиентом. Безопасный обмен ключами и все сложности обмена данными берет на себя ядро Linux.
• TCP_ENCAP_ESPINTCP — ESP в TCP поверх UDP.
• UDP_NO_CHECK6_TX — отключить отправку контрольной суммы для UDP при работе поверх IPv6.
• UDP_NO_CHECK6_RX — отключить прием контрольной суммы для UDP поверх IPv6.
• UDP_SEGMENT — включить разгрузку сегментации, установив размер сегмента GSO, то есть размер полезной нагрузки дейтаграммы, исключая UDP-заголовок. Может принимать значения от 0 до USHRT_MAX. Разгрузка сегментации снижает стоимость отправки данных за счет передачи нескольких дейтаграмм как одного большого пакета, даже если он превышает MTU. Доступна с Linux 4.18.
• UDP_GRO — включить GRO UDP. Если этот параметр включен, сокет может получать данные в нескольких дейтаграммах как один большой буфер вместе с управляющим сообщением, которое содержит размер сегмента. Доступна с Linux 5.0.
При включении GSO большой пакет разбивается на дейтаграммы, согласно MTU как можно позже:
1. Аппаратно сетевым адаптером, если эта возможность поддерживается.
2. Программно в ядре до вычисления контрольных сумм.
Опция UDP_GRO является аналогом UDP_SEGMENT для принимаемых данных. Она позволяет снизить цену приема данных за счет обработки нескольких дейтаграмм как одного большого пакета, даже если он превышает MTU.
Внимание! При использовании GSO в UDP размер сегмента должен быть выбран так, чтобы за один вызов отправлялось не более 64 дейтаграмм.
Все опции, кроме первой, присутствуют только в Linux.
Из главы 7 вы могли заметить, что UDP и UDP6 — разные протоколы, то есть UDP6 — это не просто «UDP поверх IPv6». Но для удобства их опции перечислены в одном разделе.
Опции протокола UDP Lite, рассмотренного в книге 2. Для установки опций требуется указать level, равный IPPROTO_UDPLITE.
В Unix-подобных ОС для C++ достаточно включить заголовочный файл sys/socket.h.
На этом уровне всего две опции:
• UDPLITE_SEND_CSCOV — установить или получить количество байтов отправляемой дейтаграммы, которое покрывается контрольной суммой. Если значение 0 — покрывается вся дейтаграмма.
• UDPLITE_RECV_CSCOV — установить или получить число байтов принимаемой дейтаграммы, которое покрывается контрольной суммой. Если эта опция включена, ядро будет отбрасывать дейтаграммы, которые имеют покрытие меньше заданного.
Для установки опций требуется указать level, равный IPPROTO_TCP или SOL_TCP.
В Unix-подобных ОС для C++ необходимо включить заголовочный файл netinet/tcp.h.
Внимание! Для TCP важно отметить, что большинство опций, установленных на прослушивающем сокете, будет унаследовано сокетом, возвращаемым accept().
Допустимы следующие опции:
• TCP_CONGESTION — строка, задающая алгоритм управления перегрузкой TCP для сокета. Непривилегированные процессы ограничены выбором одного из алгоритмов, указанных в /proc/sys/net/ipv4/tcp_allowed_congestion_control. Обычно это reno и cubic. Процессы с привилегией CAP_NET_ADMIN могут выбрать любой доступный алгоритм.
• TCP_MAXSEG — установить максимальный размер сегмента данных — MSS, то есть максимальное количество данных, отправляемых TCP в каждом сегменте. Нельзя установить больше, чем MTU.
• TCP_NODELAY — не задерживать отправку данных. Если параметр установлен, отключается алгоритм буферизации — . Система будет отправлять данные сразу, как только они поступают. В результате могут отправляться слишком маленькие пакеты. Если параметр не установлен, данные буферизируются до тех пор, пока их не будет достаточно для отправки.
• TCP_NOOPT — не использовать опции TCP, которые были приняты в полях Options пакета, такие как максимальный размер сегмента, размер окна TCP и т.д.
• TCP_NOPUSH в BSD-системах или TCP_CORK в Linux — отключить немедленную отправку данных. Эта опция взаимоисключающая с TCP_NODELAY. Если она установлена, данные будут отправлены после ее отключения.
При установленной опции TCP_CORK данные также могут передаваться, когда внутренний буфер заполнится либо когда соединение будет закрыто.
В Linux по умолчанию задано ограничение в 200 миллисекунд, в течение которого при установке TCP_CORK передача не производится. Если это время истекло, данные из очереди передаются автоматически.
Отдельно рассмотрим опции, которые не портируются и доступны только в Linux. Их не следует использовать в коде, предназначенном для переноса на другие ОС:
• TCP_DEFER_ACCEPT — разрешить пробуждение сокета в режиме ожидания подключения только при поступлении данных. Принимает целое число секунд. Вызов accept() не будет пробуждаться, когда подключается клиент, а в течение указанного времени будет ожидать данные от него. В некоторых случаях это может улучшить производительность.
• TCP_NOTSENT_LOWAT — ограничить количество неотправленных байтов для сокета. Уменьшает использование памяти ядра.
• TCP_LINGER2 — задать время жизни сокетов в состояние FIN_WAIT2, то есть ожидающих ACK на свой FIN. По умолчанию — 60 секунд, но может быть переопределено на уровне системы.
• TCP_QUICKACK — переключить режим отложенного подтверждения. Если опция включена, ACK-пакеты отправляются немедленно и не откладываются.
• TCP_SYNCNT — задать количество повторных передач SYN, которые TCP должен отправить, прежде чем прервать попытку подключения. Максимальное значение — 255.
• TCP_WINDOW_CLAMP — ограничить размер окна приема. По умолчанию ядро устанавливает минимальный размер SOCK_MIN_RCVBUF / 2.
• TCP_ULP — установить протокол верхнего уровня: Upper Level Protocol, или ULP. Принимает строку, например tls. Позволяет использовать TLS на уровне ядра. Подробнее см. в книге 3.
• TCP_INQ — если эта опция установлена, recvmsg() передаст количество доступных байтов для чтения приложением из сокета через управляющее сообщение TCP_CM_INQ.
• TCP_ZEROCOPY_RECEIVE — получить адреса, в которые будут записаны принятые данные. Часть не копирующего API для TCP.
Внимание! Опция TCP_QUICKACK сбрасывается операциями стека. То есть, например, после вызова send() или recv() ее необходимо установить повторно.
В Linux имеется несколько полезных информационных опций:
• TCP_INFO — используется для получения информации о сокете. Ядро вернет структуру tcp_info, определенную в linux/tcp.h и содержащую метрики и прочие характеристики состояния TCP. В MacOS X эта константа называется TCP_CONNECTION_INFO.
• TCP_CC_INFO — используется для получения статистики алгоритма предотвращения перегрузок. Опция только для чтения. Принимает указатель на объединение tcp_cc_info.
Структуру tcp_info мы приводить не будем — в ней очень большое количество полей, а опция, которая эту структуру возвращает, используется не часто, поэтому заинтересованный читатель может найти информацию о ней самостоятельно.
Объединение tcp_cc_info приведем для примера. Оно состоит из набора структур, которые описывают статистику алгоритмов предотвращения перегрузок:
#include <linux/inet_diag.h>
// Статистика алгоритма Vegas.
struct tcpvegas_info
{
__u32 tcpv_enabled;
__u32 tcpv_rttcnt;
__u32 tcpv_rtt;
__u32 tcpv_minrtt;
};
// Статистика алгоритма DCTCP.
struct tcp_dctcp_info
{
__u16 dctcp_enabled;
__u16 dctcp_ce_state;
__u32 dctcp_alpha;
__u32 dctcp_ab_ecn;
__u32 dctcp_ab_tot;
};
// Для алгоритма BBR.
struct tcp_bbr_info
{
// u64 bw: максимальная полоса пропускания в байтах.
// Нижние 32 бита, следующее поле — верхние 32 бита.
__u32 bbr_bw_lo;
__u32 bbr_bw_hi;
// Минимально фильтруемый RTT.
__u32 bbr_min_rtt;
__u32 bbr_pacing_gain;
__u32 bbr_cwnd_gain;
};
Само объединение, передаваемое в опцию:
union tcp_cc_info
{
struct tcpvegas_info vegas;
struct tcp_dctcp_info dctcp;
struct tcp_bbr_info bbr;
};
Назначение опций аналогично назначению расширенных функций connect():
• TCP_FASTOPEN — включить RFC 7413 «TCP Fast Open». Позволяет отправлять и принимать данные в SYN-пакете. Значение — количество ожидающих в очереди входящих SYN-сегментов, что по смыслу похоже на аргумент backlog в функции listen().
• TCP_FASTOPEN_CONNECT — включить альтернативный режим TFO. Если это первое соединение, connect() отправит в SYN-пакете SYN-cookie. Если же cookie уже есть, connect() добавит его в SYN и отложит соединение до первого вызова send(), который запишет отправляемые данные в SYN.
• TCP_FASTOPEN_KEY — установить значение cookie для TFO.
• TCP_FASTOPEN_NO_COOKIE — включить TFO без cookie.
Когда режим TFO включен, первый send() или recv() с флагом MSG_FASTOPEN соответственно отправит или примет данные. Подробнее TFO и расширенные функции соединения рассматриваются в книге 2.
В ОС Windows TFO будет работать только с функцией ConnectEx(), рассматриваемой в книге 2.
Несколько Linux-специфичных опций, которые позволяют серверу получать SYN-заголовки подключающихся по TCP клиентов:
• TCP_SAVE_SYN — записывать заголовки SYN-пакетов для новых соединений. Флаг типа int. Может быть установлен до или после вызова listen().
• TCP_SAVED_SYN — получить записанные заголовки SYN-пакетов. Опция только для чтения. Тип значения — массив, в который будут скопированы заголовки.
Это полезно для снятия отпечатков клиента с целью определения его ОС, идентификации на основе SYN-пакетов и подобного.
Внимание! Описанные ниже опции используются только проектом CRIU. Другое прикладное их применение не предполагается. Мы приводим их только для того, чтобы вы понимали, что это такое, если они встретятся в коде.
Для бесшовной миграции работающих контейнеров, имеющих активные сетевые подключения к процессам за пределами контейнера, с одного хоста на другой необходимо, чтобы удаленная сторона не замечала разрыва соединения.
Миграция обычно производится в пространстве пользователя, а не ядра. Но для ее выполнения нужно скопировать информацию о сокете, включая структуры ядра, на другой сокет.
В Linux существует «режим восстановления», поддерживающий такую миграцию. Когда сокет находится в этом режиме, можно выполнить следующие действия:
• Прочитать из памяти ядра содержимое очередей отправки и получения данных.
• Получить максимальное значение MSS, согласованное между двумя эндпоинтами во время установки соединения.
• Закрыть соединение без уведомления удаленной стороны. Пакеты FIN или RST отправляться не будут, поэтому удаленная сторона не будет знать, что что-то изменилось.
• Привязать сокет к заданному номеру порта без некоторых обычных проверок правильности.
• Восстановить порядковые номера TCP-последовательностей.
Иными словами — выполнять прямое влияние на протокол и ОС, которое пользователю обычно недоступно.
Опции для получения данных, необходимых, чтобы осуществить миграцию:
• TCP_REPAIR — перевести сокет в «режим восстановления». Сокет должен быть либо закрыт, либо подключен, то есть соединение должно иметь состояние ESTABLISHED. Требуется привилегия CAP_NET_ADMIN.
• TCP_REPAIR_QUEUE — выбрать очередь для восстановления:
• TCP_RECV_QUEUE — очередь приема;
• TCP_SEND_QUEUE — очередь передачи.
• TCP_MAXSEG — когда соединение находится в режиме восстановления, возвращает максимальное согласованное значение MSS, а не текущее активное значение.
• TCP_REPAIR_WINDOW — установить или получить параметры окна TCP.
Параметры окна в ядре описаны структурой:
struct tcp_repair_window
{
uint32_t snd_wl1;
uint32_t snd_wnd;
uint32_t max_window;
uint32_t rcv_wnd;
uint32_t rcv_wup;
};
Обычно поля в данной структуре для задачи восстановления менять не требуется. Но их назначение понятно из рис. 8.2.
Рис. 8.2. Окно TCP
После установки опции TCP_REPAIR_QUEUE с соответствующим параметром для чтения содержимого очереди необходимо вызвать recvmsg().
Как только данные миграции получены, а сокет «тихо закрыт», без отправки пакетов, необходимо установить соединения на новом узле.
Чтобы восстановить сокет, требуется выполнить следующие действия, как показано на рис. 8.3:
1. Создать новый сокет.
2. Перевести его в режим восстановления.
3. Привязать к правильному номеру порта.
4. Вызвать TCP_REPAIR_QUEUE со значениями TCP_RECV_QUEUE и TCP_SEND_QUEUE, а затем вызвать sendmsg() для восстановления содержимого очередей отправки и получения.
5. Восстановить порядковые номера последовательностей отправки и получения.
6. Восстановить согласованный MSS, максимальный размер активного сегмента, размер окна и возможность использования функций выборочного подтверждения и отметки времени.
Рис. 8.3. Восстановление сокета
Опции для установки данных, требуемых в целях миграции:
• TCP_QUEUE_SEQ — установить числа последовательностей. Требует предварительного выбора очереди с помощью TCP_REPAIR_QUEUE.
• TCP_REPAIR_OPTIONS — позволяет установить параметры, перечисленные в пункте 6.
Механизм восстановления и практика использования данных опций сложнее, чем описано здесь, и так как они имеют крайне ограниченное применение, подробно мы их рассматривать не будем. Заинтересованные читатели могут изучить код проекта , в частности его библиотеки .
Как только сокет восстановлен до состояния, приближенного к тому, которое существовало на старом хосте, вызывается функция connect().
В режиме восстановления соединение переходит в состояние ESTABLISHED без взаимодействия с удаленным абонентом.
На последнем этапе, когда сокет выводится из режима восстановления, отправляется TCP-сегмент, имеющий нулевой размер окна, — зонд окна для возобновления трафика между двумя эндпоинтами. После этого с данным сокетом можно работать как обычно.
При использовании опции TCP_REPAIR_WINDOW можно обойтись без зондирования.
Тонкие потоки — это такие потоки данных, при которых вся пропускная способность канала не задействована, а данные поступают редко. Подробнее о них будет рассказано в книге 2.
По умолчанию оптимизация работы с тонкими потоками выключена. Однако существует несколько опций для повышения эффективности работы с ними:
• TCP_THIN_LINEAR_TIMEOUTS — использовать линейные тайм-ауты для тонких потоков.
• TCP_THIN_DUPACK — включить повторную передачу после первого же DUP ACK.
Обе опции — это флаги со значением 0 или 1, поэтому имеют типы int. Они присутствуют только в Linux.
По умолчанию, если для TCP-сокетов установлена опция SO_KEEPALIVE и активность отсутствовала в течение 2 часов, система отправит пакет keep-alive.
Если в течение 1 секунды в ответ будет получен ACK, система продолжит ожидание. Если в ответ будет получено RST- или ICMP-сообщение Host Unreachable, соединение будет разорвано. Иначе через каждые 75 секунд будет отправляться некоторое количество сообщений:
• 8 — в Unix-подобных ОС;
• 5 — в старых версиях ОС Windows;
• 10 — в новых ОС Windows.
При отсутствии на них ответа соединение будет разорвано. Параметры keep alive также можно установить через опции.
Опции, общие для Windows и Linux:
• TCP_KEEPCNT — максимальное количество запросов keep-alive, которые TCP должен отправить перед разрывом соединения.
• TCP_KEEPINTVL — количество секунд между отправкой пакетов keep-alive.
• TCP_KEEPIDLE — количество секунд, в течение которого соединение должно оставаться бездействующим, прежде чем TCP начнет отправлять пакеты keep-alive, если для сокета была установлена опция сокета SO_KEEPALIVE. В ОС Windows доступно второе название опции — TCP_KEEPALIVE.
Данные опции поддерживаются не всеми Unix-подобными системами, но они присутствуют как в Linux, так и в ОС Windows, начиная с версии 10.
В ОС Windows параметры keep-alive задаются в подразделе реестра HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters:
• KeepAliveTime — частота отправки сообщений проверки активности протоколом TCP, если удаленная система отвечает по данному протоколу. Значение по умолчанию — 2 часа.
• KeepAliveInterval — частота повторения передачи активности при отсутствии ответа. Значение по умолчанию — 1 секунда.
Значения параметров задаются в миллисекундах.
Количество проб keep-alive в Windows версии 10 и позже жестко задано и составляет 10.
В Linux существует набор опций для работы с тайм-аутами в TCP:
• TCP_USER_TIMEOUT — если значение больше 0, задает максимальное количество миллисекунд, в течение которого передаваемые данные могут оставаться не подтвержденными или буферизованные данные могут оставаться не переданными, например, из-за нулевого размера окна, прежде чем TCP принудительно закроет соответствующее соединение и вернет ошибку ETIMEDOUT. Нулевое значение означает, что TCP использует системное значение по умолчанию.
• TCP_TIMESTAMP — управление опцией Timestamps в заголовке TCP, показанной на рис. 8.4. Позволяет установить или прочитать опцию. Младший бит содержит бит включения значения TSVal в микросекундах. Опция присутствует только в Linux.
• TCP_TX_DELAY — установить задержку передачи каждого пакета в микросекундах. Тип опции — unsigned int.
В ОС Windows опция TCP_USER_TIMEOUT называется TCP_MAXRT, во FreeBSD — TCP_KEEPINIT, в MacOS X — TCP_CONNECTIONTIMEOUT. Подробнее о них будет рассказано в книге 2.
Рис. 8.4. TCP-опция timestamp — метка времени
В ядре Linux предусмотрена :
• TCP MD5 — описана в RFC 2385 «Protection of BGP Sessions via the TCP MD5 Signature Option».
• TCP AO — описана в RFC 5925 «The TCP Authentication Option» и RFC 5926 «Cryptographic Algorithms for the TCP Authentication Option (TCP-AO)». В TCP AO для обеспечения аутентификации используется цепочка ключей.
Обычно TCP-аутентификация используется при общении маршрутизаторов по BGP или сходным протоколам. Чтобы ее реализовать, добавляется новый TCP-заголовок, содержащий код аутентификации сообщения — Message Authentication Code, или MAC.
Внимание! Не следует путать MAC с MAC-адресом. Это одинаковые аббревиатуры с разным значением.
Опции TCP MD5:
• TCP_MD5SIG — добавить подпись MD5 для TCP-сокета.
• TCP_MD5SIG_EXT — установить длину префикса адреса для расчета подписи, которая будет распространяться на диапазон адресов. Если эта опция активирована и в tcpm_flags установлен флаг TCP_MD5SIG_FLAG_PREFIX, при расчете подписи будет учитываться длина префикса адреса.
TCP MD5 работает со следующей структурой:
#include <linux/tcp.h>
struct tcp_md5sig
{
// Ассоциированный адрес.
struct __kernel_sockaddr_storage tcpm_addr;
// Флаги расширения.
__u8 tcpm_flags;
// Длина префикса адреса.
__u8 tcpm_prefixlen;
// Длина ключа.
__u16 tcpm_keylen;
// Индекс интерфейса.
int tcpm_ifindex;
// Ключ в бинарном виде.
__u8 tcpm_key[TCP_MD5SIG_MAXKEYLEN];
};
Аутентификация TCP MD5 использует один ключ на соединение и не имеет защиты от таких атак, как повторение трафика.
Внимание! Этот вид аутентификации является устаревшим.
Заменяет его защищенная от атак TCP AO.
Опции TCP AO:
• TCP_AO_ADD_KEY — добавить главный ключ Master Key Tuple, или MKT.
• TCP_AO_DEL_KEY — удалить MKT.
• TCP_AO_REPAIR — в режиме восстановления получить SNE и ISN.
• TCP_AO_GET_KEYS — получить MKT.
• TCP_AO_INFO — установить или получить опции TCP-AO для каждого сокета.
Здесь:
• SNE — расширения порядковых номеров, которые используются для предотвращения атак повторного воспроизведения.
• ISN — начальные порядковые номера эндпоинта, на основе которых генерируются уникальные ключи защиты трафика, даже если MKT общий.
Чтобы ядро поддерживало TCP AO, оно должно быть скомпилировано с опцией CONFIG_TCP_AO. Без этого будут доступны опции чтения TCP_AO_GET_KEYS и TCP_AO_INFO, но не опции установки ключей.
Аналогично для поддержки TCP MD5 должна быть включена опция CONFIG_TCP_MD5SIG.
Для установки опций требуется указать level, равный IPPROTO_RAW или SOL_IP:
• IP_HDRINCL — эту опцию мы уже рассматривали. Если она включена, при отправке IP-дейтаграммы пользователь должен сам заполнить некоторые поля IP-заголовка, например значение протокола. Обратите внимание, что данная опция имеет другое название для сокетов IPv6.
• ICMP_FILTER — определяет, какие типы ICMP-сообщений будет принимать сокет. Эта опция может быть использована, только если для raw-сокета указан протокол IPPROTO_ICMP.
На уровне IP для raw-сокетов также существуют опции, описанные выше, например IP_NODEFRAG.
Представляют собой опции сокетов AF_PACKET более низкого уровня, чем raw-сокеты, и доступного только в Linux. Для установки данных опций требуется указать level, равный SOL_PACKET:
• PACKET_ADD_MEMBERSHIP и PACKET_DROP_MEMBERSHIP — добавить или удалить привязку. Настройка многоадресной рассылки и неразборчивого режима на физическом уровне. Аргумент — структура package_mreq. Используется в снифферах, которые мы разберем в главе 22.
• PACKET_AUXDATA — флаг включения передачи метаданных вместе с каждым пакетом. При включении данной опции будет передаваться вспомогательное сообщение, содержащее состояние пакета, например факт его корректности, длину, MAC и т.п.
• PACKET_FANOUT — режим масштабирования обработки по потокам:
• PACKET_FANOUT_HASH — отправить пакеты из одного и того же потока в один и тот же сокет. Для каждого пакета сокет выбирается по хешу от сетевого адреса и полям порта транспортного уровня.
• PACKET_FANOUT_LB — циклический алгоритм балансировки нагрузки.
• PACKET_FANOUT_CPU — выбрать сокет в зависимости от процессора, на который прибыл пакет.
• PACKET_FANOUT_ROLLOVER — обрабатывать все данные в одном сокете, переходя к следующему, когда сокет перегружен.
• PACKET_FANOUT_RND — выбрать сокет случайно.
• PACKET_FANOUT_QM — выбрать сокет, используя записанное значение queue_mapping полученного skb. Здесь queue_mapping — 16-битное число, которое можно установить, используя утилиту tc. Подробнее см. man 8 tc-skbedit. Иными словами, это явный выбор очереди для сокета.
• PACKET_LOSS — пропустить некорректный пакет. Если флаг установлен, любой неправильно сформированный пакет будет пропущен, его tp_status будет сброшен на TP_STATUS_AVAILABLE и передача данных продолжится. В противном случае, когда в кольцевом буфере передачи встречается некорректный пакет, его tp_status сбрасывается на TP_STATUS_WRONG_FORMAT, а передача немедленно прерывается. Чтобы ее возобновить, требуется исправить ошибку, сбросить tp_status в TP_STATUS_SEND_REQUEST и перезапустить процесс передачи через send().
• PACKET_RESERVE — зарезервировать место после метаданных и выравнивания. По умолчанию кольцо приема записывает пакеты сразу после структуры метаданных и выравнивания.
• PACKET_RX_RING — создать отображаемый в память кольцевой буфер для асинхронного приема пакетов.
• PACKET_TX_RING — создать отображаемый в память кольцевой буфер для передачи пакетов. Опция аналогична PACKET_RX_RING и принимает те же аргументы.
• PACKET_STATISTICS — получить статистику. Ее получение обнулит внутренние счетчики.
• PACKET_TIMESTAMP — выбрать тип метки времени. Кольцо приема сохраняет метку в метаданных. По умолчанию это метка, генерируемая, когда пакет копируется в кольцо. Помимо значения по умолчанию, поддерживает два аппаратных формата, описанных в документации ядра в networking/timestamping.rst.
• PACKET_VERSION — выбрать версию кольца приема. По умолчанию PACKET_RX_RING создает кольцо приема версии TPACKET_V1. Установить число перед созданием кольца можно, изменив версию.
• PACKET_QDISC_BYPASS — отключить буферизацию на уровне дисциплины очередей. По умолчанию пакеты проходят через уровень qdisc, но для некоторых случаев, например генераторов трафика, это поведение может быть полезно отключить. Это приведет к увеличению количества ошибок, когда очереди передачи сетевых устройств заняты.
По историческим причинам эти параметры сокета указываются с level-типом SOL_SOCKET, хотя они специфичны для AF_UNIX.
В Unix-подобных ОС для C++ необходимо включить заголовочный файл netinet/un.h.
Большая часть этих параметров возвращает данные о дескрипторах и процессах:
• SO_PASSCRED — включение этой опции сокета приводит к получению учетных данных отправляющего процесса во вспомогательном сообщении SCM_CREDENTIALS с каждым последующим сообщением. Возвращаемые учетные данные — это данные, которые указаны отправителем с помощью SCM_CREDENTIALS, или значения по умолчанию: PID отправителя, реальные идентификаторы пользователя и группы.
• SO_PASSSEC — разрешить получение метки безопасности SELinux однорангового сокета во вспомогательном сообщении типа SCM_SECURITY.
• SO_PEEK_OFF — при отрицательном значении recv() с флагом MSG_PEEK будет получать данные из начала очереди. Если для параметра установлено значение, большее или равное 0, следующий просмотр данных, находящихся в очереди в сокете, будет происходить со смещением в байтах, равным значению параметра. Смещение будет увеличиваться на количество байтов, которые были просмотрены из очереди, и следующий просмотр вернет следующие данные из очереди. Если данные удаляются из начала очереди с помощью вызова recv() без флага MSG_PEEK, смещение будет уменьшено на число удаленных байтов. В дейтаграммных сокетах, если смещение указывает на середину дейтаграммы, возвращаемые данные будут отмечены флагом MSG_TRUNC. По умолчанию –1.
• SO_PASSPIDFD — включить или выключить получение сообщения SCM_PIDFD, содержащего pidfd для отправляющего процесса.
• SO_PEERCRED — вернуть учетные данные однорангового процесса, подключенного к этому сокету. Параметр только для чтения.
• SO_PEERPIDFD — аналог SO_PEERCRED, который возвращает pidfd удаленного абонента вместо обычного pid, что позволяет разработчику не заботиться о проблеме повторного использования PID. Опция только для чтения. Тип возвращаемого значения — int.
• SO_PEERGROUPS — получить вспомогательные группы удаленного абонента. Расширяет SO_PEERCRED.
• SO_PEERSEC — вернуть контекст безопасности однорангового сокета, подключенного к этому сокету. По умолчанию — то же, что и контекст безопасности процесса, создавшего одноранговый сокет, если он не переопределен. Параметр только для чтения.
Опция SO_PEERCRED вернет структуру ucred:
#include <linux/socket.h>
struct ucred
{
// PID.
__u32 pid;
// ID пользователя — владельца процесса.
__u32 uid;
// ID группы — владельца процесса.
__u32 gid;
};
Сокеты Netlink служат для обмена данными между ядром и приложениями в Linux и некоторых BSD-системах. Их мы рассмотрим в главе 11, а сейчас только перечислим опции, применимые к данному типу сокетов:
• NETLINK_ADD_MEMBERSHIP и NETLINK_DROP_MEMBERSHIP — добавить сокет в группу или удалить из группы. Используется для подписки на сообщения.
• NETLINK_LIST_MEMBERSHIPS — получить все группы, членом которых является сокет. Значение — указатель на массив uint32, а optlen — размер массива.
• NETLINK_PKTINFO — присылать сообщения, содержащие расширенную информацию о группе.
• NETLINK_BROADCAST_ERROR — если установлено, netlink_broadcast() будет считать результат ENOBUFS ошибкой.
• NETLINK_NO_ENOBUFS — клиенты, ожидающие Netlink-сообщений, не будут получать ошибку ENOBUFS, говорящую о том, что размер буфера недостаточен. Это полезно, например, когда размер буфера статически установлен и лишние сообщения отбрасываются.
• NETLINK_LISTEN_ALL_NSID — если данная опция включена, сокет будет получать сообщения Netlink из всех сетевых пространств имен, которым назначен nsid, то есть идентификатор пространства имен, в котором был открыт сокет.
• NETLINK_CAP_ACK — если опция включена и ядро не может выделить память для сообщения подтверждения, оно будет обрезать полезную нагрузку исходного сообщения, оставляя только заголовок.
В структурах ядра существуют опции, которые не относятся к уровню сокета и протоколов. Например, атрибут sk_max_pacing_rate можно установить через опцию SO_MAX_PACING_RATE. Описание этой опции мы уже привели. Она устанавливает задержку между пакетами, которую добавляет планировщик Fair Queue, чтобы ограничивать скорость. Данная опция описана в man 8 tc-fq, а не в man 7 socket, и для нее используется константа уровня SOL_SOCKET.
Логично, что данная опция специфична для Linux, причем зависит от использования конкретного планировщика. Но эта опция не единственная.
Узнать, какие опции используются для более тонкой настройки, можно из документации на компонент, используемый системой. Простого решения, к сожалению, нет, потому что стеки ОС имеют различия, и даже в одной и той же ОС существуют изменения, зависящие от версии. Остается только понять, как работает сетевая подсистема, и следить за нововведениями.
В Linux, например, набор опций можно посмотреть в исходном коде ядра:
• Опции сокетов определены в файле include/uapi/asm-generic/socket.h. Все они перечислены в функциях sk_getsockopt() и sk_setsockopt(), реализованных в файле source/net/core/sock.c.
• Опции IP определены в файле include/uapi/linux/in.h. А их получение и установка выполняются функциями do_ip_getsockopt(), do_ip_setsockopt(), реализованными в файле source/net/ipv4/ip_sockglue.c.
Опции других уровней и протоколов находятся в других файлах, но принцип их поиска к настоящему моменту должен быть понятен.
В BSD-системах и в Linux, особенно в системах типа Plan 9, дескрипторы сокетов аналогичны файловым дескрипторам, и поэтому к ним можно применять функцию fcntl(), специфичную для Unix-подобных систем. Функция fcntl() управляет файловым дескриптором и объявлена в файле fcntl.h.
Это может быть полезно для разных целей. Например, мы использовали данную функцию в главе 6, когда рассматривали внеполосные данные, чтобы установить идентификатор процесса для приема сигнала SIGURG.
Прототип функции:
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
Параметры функции fcntl():
• fd — файловый дескриптор, в нашем случае дескриптор сокета.
• cmd — выполняемая над дескриптором операция.
Остальные аргументы функции зависят от конкретной операции.
Функция в случае неудачи возвращает –1 или значение, которое зависит от выполненной операции.
Функция способна выполнять многие операции, напрямую к сокетам не относящиеся: дублирование файлового дескриптора, блокировку на чтение или запись и подобные. Здесь мы не будем их рассматривать, но читатель при желании всегда может обратиться к man 2 fcntl и man 3 fcntl.
С точки зрения сокетов интерес представляют несколько команд:
• F_GETFD и F_SETFD — получить или установить флаги дескриптора. Позволяет установить или сбросить флаг O_CLOEXEC.
• F_GETFL и F_SETFL — получить или установить флаги статуса. Используется для установки неблокирующего режима в Linux через флаг O_NONBLOCK (этот вопрос мы рассмотрим в следующих книгах). Ранее был рассмотрен ioctl, выполняющий аналогичную функцию.
• F_GETOWN и F_SETOWN либо F_GETOWN_EX и F_SETOWN_EX — получить или установить процесс или группу для получения сигналов SIGURG и SIGIO.
• F_GETSIG и F_SETSIG — получить или установить сигнал, который будет отправлен при доступности ввода или вывода. По умолчанию — SIGIO. Операция установки заменяет его на другой или возвращает SIGIO, если в качестве аргумента был передан 0.
• F_GETPIPE_SZ и F_SET_PIPE_SZ — получить или установить число байтов в буфере канала. Минимальное значение — размер страницы. Максимальное значение для непривилегированного процесса записано в /proc/sys/fs/pipe-max-size.
Эта же функция есть и в Python в упомянутом ранее модуле fcntl:
import fcntl
@overload
def fcntl(fd: FileDescriptorLike, cmd: int, arg: int = 0, /) -> int
@overload
def fcntl(fd: FileDescriptorLike, cmd: int, arg: str | ReadOnlyBuffer, /) -> bytes
Функция доступна только для Unix-подобных систем, в ОС Windows ее нет.
Рассмотрим пример, в котором сокет переводится в неблокирующий режим через fcntl():
#include <fcntl.h>
...
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int non_block_enabled = 1;
if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) < 0)
{
std::cerr << sock_wrap.get_last_error_string() << std::endl;
return EXIT_FAILURE;
}
Видим два вызова: первый — для получения флагов, второй — для их установки. Это сделано для того, чтобы случайно не изменить флаги, которые менять не требуется. Выполнение того же действия мы уже рассматривали с использованием ioctl().
В операционной системе часто предусмотрено несколько вариантов выполнения одного и того же действия: в данном случае fcntl(), как правило, работает поверх ioctl(), но есть и параллельные варианты, использующие разные системные вызовы. Как уже было сказано, выбирайте тот, который лучше подходит для решения вашей задачи.
Например, если требуется портируемый код, используйте наиболее стандартный путь, который поддерживается разными системами. Если же требуется быстро реализовать прототип, используйте инструмент, с которым вы лучше всего знакомы и о котором легче найти информацию.
Для специфических решений, которые требуют высокой производительности, допустимо использовать редкие и сложные в понимании оптимизации, зависимые от платформы, если иначе задачу решить не удается.
Операционная система помимо адреса, протокола и типа привязывает к сокету множество дополнительных параметров или опций. Они позволяют разработчикам тонко настраивать поведение приложений в соответствии со специфическими требованиями задачи.
Эти опции могут быть как общими между разными ОС, например Linux и Windows, так и специфичными для конкретной системы.
Чтобы управлять опциями, используются следующие функции:
• setsockopt() — установить значение опции на уровне библиотеки сокетов или на уровне протокола.
• getsockopt() — получить значение опций сокета.
• sendmsg() — отправить управляющие данные.
• recvmsg() — принять управляющие данные.
Опции могут быть установлены на разных уровнях, от уровня сокета до уровней различных протоколов. Уровень задается константой. Для протоколов она, как правило, равна их коду, но имеет и специальное имя, например IPPROTO_IP и SOL_IP.
Опции уровня IP позволяют работать с группами многоадресной рассылки, управлять связыванием адресов, фрагментацией пакетов и т.п.
Состав опций уровня IPv6 по смыслу частично повторяет уровень IP, но с некоторыми отличиями: константы, сходные по смыслу, могут иметь префикс IPV6_; кроме того, на данном уровне присутствуют специфичные опции. Для установки параметров IPv6-сокета также используется часть опций уровня IP.
Похожим образом устроены опции уровней UDP и UDPLite.
На уровне TCP можно управлять подключением, восстановлением соединения, функциями keep-alive, тайм-аутами, а также включать и отключать различные оптимизации, которые зависят от свойств потоков данных.
Свой набор опций существует и для низкоуровневых сокетов, таких как raw-сокеты IP-семейства и сокеты PF_PACKET.
Опции для Unix-сокетов позволяют, к примеру, изменять параметры безопасности и получать дополнительную информацию о процессах, ими владеющих.
Опции для сокетов Netlink, помимо установки параметров, позволяют работать с группами многоадресной рассылки, что важно для этого типа сокетов. Их установка дает возможность реализовывать мониторы различных событий, происходящих в системе.
Существуют также опции, не принадлежащие к уровню сокета, но новую константу уровня вводить нежелательно, и поэтому разработчики часто добавляют их на уровень SOL_SOCKET.
Некоторые из опций вызывают отправку управляющих сообщений со вспомогательными данными, описанными в следующей главе. Такие сообщения, например, могут содержать PID процесса для сокетов AF_UNIX или опции заголовка IP для сокетов AF_INET.
Часть параметров сокета устанавливается не через опции, а через ioctl, которые будут рассмотрены в главе 10.
В BSD-системах и в Linux дескрипторы сокетов аналогичны файловым дескрипторам, поэтому к ним можно применять функцию fcntl(), специфичную для Unix-подобных систем.
Функции setsockopt() и ioctl(), несмотря на свое назначение, представляют собой лабиринты параметров и команд, которые могут быстро запутать неподготовленного разработчика. Однако для создания высокопроизводительных и надежных сетевых приложений важно уметь работать с опциями правильно.
В следующих главах мы подробнее рассмотрим, как изменения в описанных параметрах могут повлиять на производительность приложений, а также узнаем, как применять эти знания для решения практических задач.
1. Что такое опция сокета?
2. Зачем обычно нужно считывать опции сокета перед их установкой?
3. Каким образом можно изменять опции сокетов?
4. Для чего нужны уровни опций и какие они бывают? Приведите два-три примера уровней.
5. Бывают ли опции, не относящиеся к определенному уровню? Если да, как их получить или установить?
6. Назовите три–пять, которые, по вашему мнению, были бы вам полезны в разработке сетевых приложений. В каких случаях вы будете использовать эти опции?
7. Встретились ли среди названных в ответе на предыдущий вопрос опции, специфичные для конкретной ОС? Что вы сделаете при переносе приложения туда, где нужной опции нет?
8. Что делает опция TCP_NODELAY? Когда ее использование может быть полезным или вредным?
9. Назовите хотя бы два метода определения состояния подключения.
10. Использование каких опций позволяет управлять TCP-соединением?
11. Как изменение размера буфера отправки или получения влияет на производительность обмена данными?
12. Что такое бесшовная миграция?
13. Зачем нужно отображение буферов отправки и передачи в пространство пользователя для пакетных сокетов?
14. Какое значение параметра level требуется указать для установки опций raw-сокетов?
15. Зачем используется функция fcntl()?
16. Напишите приложение, которое с помощью getsockopt() получает текущее значение МТU сокета и устанавливает новое.