sendmsg() не работает с неверным аргументом при отправке UDP-пакета со вспомогательными данными IP_TOS

Я пытаюсь отправить пакет UDP со значением TOS, установленным с помощью sendmsg() в Linux с ядром 2.6.18. Но вызов завершается с ошибкой «Неверный аргумент». Если я отключу вспомогательную часть данных (см. флаг компиляции условий USE_IP_TOS), то sendmsg() завершится успешно. Ниже приведен код и вывод:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <net/if.h>

#define UDP_PORT 45897

int SendMsgWithIPTOS(int sockfd, unsigned char *pBuf, size_t bufLen, int tos)
{
    unsigned int toIp;
    struct sockaddr_in to_addr;

    struct msghdr msg;
    struct iovec iov[1];
    unsigned char cmsg[ CMSG_SPACE(sizeof(int)) ];
    struct cmsghdr *cmsgptr = NULL;

    size_t sendBytes = 0;

    toIp = 0x0a214401;
    to_addr.sin_addr.s_addr = htonl(toIp);
    to_addr.sin_family = AF_INET;
    to_addr.sin_port = htons(UDP_PORT);

    memset(&iov, 0, sizeof(iov));
    memset(&cmsg, 0, sizeof(cmsg));

    iov[0].iov_base = pBuf;
    iov[0].iov_len = bufLen;

    memset(&msg, 0, sizeof (struct msghdr));
    msg.msg_name = &to_addr;
    msg.msg_namelen = sizeof(to_addr);
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

#ifndef USE_IP_TOS
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
#else
    msg.msg_control = cmsg;
    msg.msg_controllen = sizeof(cmsg);
    cmsgptr = CMSG_FIRSTHDR(&msg);
    cmsgptr->cmsg_level = IPPROTO_IP;
    cmsgptr->cmsg_type  = IP_TOS;
    cmsgptr->cmsg_len  = CMSG_LEN(sizeof(int));
    memcpy(CMSG_DATA(cmsgptr), (unsigned char*)&tos, sizeof(int));
    //*((int *) CMSG_DATA (cmsgptr)) = tos;
    msg.msg_controllen = CMSG_SPACE(sizeof(int));
#endif

    msg.msg_flags = 0;

    if ((sendBytes = sendmsg(sockfd, &msg, 0)) == -1)
    {
        fprintf(stderr, "sendmsg() failed (%s) (%d)\n", strerror(errno), errno);
    }
    else
    {
        fprintf(stderr, "sent %d bytes \n", sendBytes);
    }

    return sendBytes;
}

int main()
{
    int32_t sockfd, retCode;
    struct sockaddr_in sockaddr;
    socklen_t addrlen = sizeof(sockaddr);
    unsigned char buf[1024] = { 0 };
    size_t len = sizeof(buf);
    int optval;

    /* Create a UDP socket */
    if((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
    {
        fprintf(stderr, "socket failed (%s)\n", strerror(errno));
        return sockfd;
    }

    /* Bind the socket to the given port */
    memset(&sockaddr, 0, addrlen);
    sockaddr.sin_addr.s_addr = INADDR_ANY;
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(UDP_PORT);

    if ((retCode = bind(sockfd, (const struct sockaddr *)&sockaddr, addrlen)) < 0)
    {
        fprintf(stderr, "bind failed (%s)\n", strerror(errno));
        close(sockfd);
        return retCode;
    }

#if 0   // set the DSCP value using setsockopt
    /* Set the DSCP value to Expedited Forwarding (0x2e) */
    optval = 0xb8;
    if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, (void*)&optval, sizeof(optval)) < 0)
    {
        fprintf(stderr, "Failed to set DSCP value (%s)\n", strerror(errno));
        close(sockfd);
        return retCode;
    }
    else
    {
        socklen_t optlen = sizeof(optval);
        fprintf(stderr, "DSCP value to set to %u\n", (optval >> 2));
        optval = 0;
        if (getsockopt(sockfd, IPPROTO_IP, IP_TOS, (void*)&optval, &optlen) < 0)
        {
            fprintf(stderr, "Failed to retrieve ip_tos value\n");
        }
        else
        {
            fprintf(stderr, "Retrieved dscp value %u optlen (%d)\n", (optval >> 2), optlen);
        }
    }
#endif

    optval = 0xb8; // Expedited Forwarding
    while (1)
    {
        retCode = SendMsgWithIPTOS(sockfd, buf, len, optval);
        sleep(1);
    }

    return retCode;
}

Output:
-------
$
$ gcc -o ip_tos sockopt_test.c -Wall -DUSE_IP_TOS
$
$ ./ip_tos
sendmsg() failed (Invalid argument) (22)
sendmsg() failed (Invalid argument) (22)

$
$ gcc -o ip_tos sockopt_test.c -Wall
sockopt_test.c: In function ‘SendMsgWithIPTOS’:
sockopt_test.c:22: warning: unused variable ‘cmsgptr’
$
$ ./ip_tos
sent 1024 bytes
sent 1024 bytes

$

Обратите внимание, что я пытался включить/отключить часть кода setsockopt() для целей тестирования. Но это не помогает

Я не знаю, что я делаю неправильно. Любая помощь в поиске проблемы с кодом или какой-либо указатель для дальнейшей отладки проблемы приветствуется.

Спасибо, Равендра.


person Raveendra Joshi    schedule 06.03.2017    source источник


Ответы (1)


Я попробовал ваш код, немного измененный (2 изменения: другой IP и макрос USE_IP_TOS определен), на моем Centos[kernel=3.10.0]=>Windows тестовые отправки - работает. Я видел в своих пакетах Wireshark UPD с полем DSCP, установленным на «ускорить пересылку».

Ваш код работает.

Однако вы не можете использовать беззнаковую переменную [size_t sendBytes] для подписанных возвращаемых значений системного вызова. memset-ы тоже бесполезны. Возможно, ваша проблема вызвана соображениями безопасности (но странная ошибка). Или ваше ядро ​​слишком старое; или поддержка этой функциональности скомпилирована из ядра. Однако система может пожаловаться на неверный ToS (например, =2) с EINVAL.

person Jackal    schedule 08.08.2017
comment
Как вы изменили код? Пожалуйста, попробуйте уточнить свой ответ. - person Rob Murray; 08.08.2017