CMSG(3)

ИМЯ
CMSG_ALIGN, CMSG_SPACE, CMSG_NXTHDR, CMSG_FIRSTHDR - доступ к вспомогательным данным

ОБЗОР
#include <sys/socket.h>

struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);
struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);
size_t CMSG_ALIGN(size_t length);
size_t CMSG_SPACE(size_t length);
size_t CMSG_LEN(size_t length);
unsigned char *CMSG_DATA(struct cmsghdr *cmsg);

ОПИСАНИЕ
Эти макросы используются для создания сообщений контроля доступа (также называемых вспомогательными данными), которые не являются частью полезной нагрузки сокета. Эта управляющая информация может содержать интерфейс, с которого получен пакет, различные редко используемые поля заголовков и расширенное описание ошибок, наборы файловых дескрипторов или параметров доступа (credentials) UNIX. Например, управляющие сообщения могут быть использованы для отправки дополнительных полей заголовков, таких, как параметры IP. Вспомогательные данные посылаются с помощью вызова sendmsg(2) и принимаются посредством вызова recvmsg(2). Более полная информация приведена в их справочных страницах.

Вспомогательные данные представляют собой последовательность структур cmsghdr с присоединёнными к ней данными. Возможные типы управляющих сообщений смотрите в соответствующих страницах протоколов. Максимально возможный размер буфера вспомогательных данных одного сокета можно установить через /proc/sys/net/core/optmem_max; смотрите socket(7).

Структура cmsghdr определена следующим образом:

    struct cmsghdr {
        size_t cmsg_len;    /* счетчик байтов данных с заголовком
                               (тип — socklen_t в POSIX) */
        int    cmsg_level;  /* начальный протокол */
        int    cmsg_type;   /* тип, зависящий от протокола */
    /* после
       unsigned char cmsg_data[]; */
    };

К последовательности структур cmsghdr нельзя обращаться напрямую. Для этого используйте следующие макросы:

*   Макрос CMSG_FIRSTHDR() возвращает указатель на первый cmsghdr в буфере вспомогательных данных, связанных с переданным msghdr.

*   Макрос CMSG_NXTHDR() возвращает следующее значение cmsghdr после переданного cmsghdr. Он возвращает NULL, если в буфере недостаточно места.

*   Макрос CMSG_ALIGN() возвращает длину, учитывая необходимое выравнивание. Это постоянное выражение.

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

*   Макрос CMSG_DATA() возвращает указатель на часть с данными в cmsghdr.

*   Макрос CMSG_LEN возвращает значение, хранящееся в поле cmsg_len структуры cmsghdr с учётом необходимого выравнивания. Он принимает значение объёма данных в качестве аргумента. Это постоянное выражение.

Чтобы создать вспомогательные данные, сначала инициализируйте элемент msg_controllen структуры msghdr, указав длину буфера управляющего сообщения. Запустите CMSG_FIRSTHDR() со структурой msghdr для получения первого управляющего сообщения и CMSG_NEXTHDR() для получения последующих. Для каждого управляющего сообщения инициализируйте cmsg_len (с помощью CMSG_LEN()) и другие поля заголовка cmsghdr, а также часть для данных (с помощью CMSG_DATA()). В результате значение поля msg_controllen структуры msghdr будет равно сумме CMSG_SPACE() для длин всех управляющих сообщений в буфере. Дополнительная информация о msghdr находится в recvmsg(2).

Если буфер управляющих сообщений слишком мал для хранения всех сообщений, то флагу MSG_CTRUNC в поле msg_flags структуры msghdr присваивается значение единицы.

СООТВЕТСТВИЕ СТАНДАРТАМ
Эта модель вспомогательных данных соответствует черновому варианту POSIX.1g, 4.4BSD-Lite, расширению IPv6 API, описанному в RFC 2292, и SUSv2. CMSG_ALIGN() — это расширение Linux.

ЗАМЕЧАНИЯ
В целях переносимости вспомогательные данные могут быть доступны только с помощью макросов, описанных здесь. CMSG_ALIGN() — это расширение Linux, поэтому оно не должно быть использовано в переносимых программах.

В Linux CMSG_LEN(), CMSG_DATA() и CMSG_ALIGN() — это постоянные выражения (вне зависимости от переданных аргументов). Вы можете воспользоваться этой особенностью для объявления размеров глобальных переменных. Но это также сделает программу непереносимой на другие платформы.

ПРИМЕР
Данный код ищет параметр IP_TTL в буфере с полученными вспомогательными данными:

    struct msghdr msgh;
    struct cmsghdr *cmsg;
    int *ttlptr;
    int received_ttl;
    /* приём вспомогательных данных в msgh */
    for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;
            cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
        if (cmsg->cmsg_level == IPPROTO_IP
                && cmsg->cmsg_type == IP_TTL) {
            ttlptr = (int *) CMSG_DATA(cmsg);
            received_ttl = *ttlptr;
            break;
        }
    }
    if (cmsg == NULL) {
        /* Ошибка: не включён IP_TTL, мал буфер или ошибка ввода-вывода */
    }

Данный код передаёт массив файловых дескрипторов через доменный сокет UNIX, используя SCM_RIGHTS:

    struct msghdr msg = { 0 };
    struct cmsghdr *cmsg;
    int myfds[NUM_FD];  /* содержит дескрипторы файлов для передачи */
    int *fdptr;
    char iobuf[1];
    struct iovec io = {
        .iov_base = iobuf,
        .iov_len = sizeof(iobuf)
    };
    union {         /* буфер вспомогательных данных, обёрнутый в union,
                       чтобы точно получить нужное выравнивание */
        char buf[CMSG_SPACE(sizeof(myfds))];
        struct cmsghdr align;
    } u;
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = u.buf;
    msg.msg_controllen = sizeof(u.buf);
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int) * NUM_FD);
    fdptr = (int *) CMSG_DATA(cmsg);    /* инициализация полезной нагрузки */
    memcpy(fdptr, myfds, NUM_FD * sizeof(int));

СМОТРИТЕ ТАКЖЕ
recvmsg(2), sendmsg(2)

RFC 2292