Сбой 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() для целей тестирования. Но это не помогает
Я не знаю, что я делаю не так. Любая помощь в поиске проблемы с кодом или некоторый указатель для дальнейшей отладки проблемы очень ценится.
Спасибо Равеендра
2 ответа
cmsg_type = IP_TOS
не поддерживается Linux 2.6.18,EINVAL
возвращается сюда: https://elixir.bootlin.com/linux/v2.6.18/source/net/ipv4/ip_sockglue.c#L194
Он был представлен в Linux 3.13-rc1 в этом изменении: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f02db315b8d888570cb0d4496cfbb7e4acb047cb.
Я попробовал ваш код, немного изменив (2 изменения: другой IP и макрос USE_IP_TOS определены), на моем Centos[kernel=3.10.0]=> тестовые отправки Windows - это работает. Я видел в своих UPD пакетах Wireshark с полем DSCP, установленным на "ускоренную пересылку".
Ваш код работает.
Хотя вы не можете использовать беззнаковую переменную [size_t sendBytes] для возвращаемых значений со знаком sys-call. memset-ы тоже бесполезны. Возможно, ваша проблема вызвана соображениями безопасности (но странно ошибаться). Или ваше ядро слишком старое; или поддержка этой функциональности скомпилирована из ядра. Система может жаловаться на плохое ToS (например, =2) с помощью EINVAL.