Что может привести к возврату SO_EE_CODE_ZEROCOPY_COPIED при отправке UDP-сообщений с MSG_ZEROCOPY?

Окружающая среда

Версия для Linux: Linux 5.4.0-4-amd64 Debian 5.4.19-1 x86_64 GNU/Linux

Разброс-собрать NIC:

scatter-gather: on tx-scatter-gather: on tx-scatter-gather-fraglist: off [fixed]

Выход

Код sock_extended_err имеет значение SO_EE_CODE_ZEROCOPY_COPIED. Согласно документу ядра Linux, когда устройства не поддерживает ввод-вывод с разбросом, этот код будет возвращен, но вы можете видеть, что моя сетевая карта поддерживает и включает ввод-вывод с разбросом.

Связанный документ должен показать официальное объяснение SO_EE_CODE_ZEROCOPY_COPIED, и linux поддерживает udp msg_zerocopy для версии >= 5.0.

Итак, есть идеи о других причинах? Или мой код неверный?

Код

#define _GNU_SOURCE
#include <arpa/inet.h>
#include <error.h>
#include <errno.h>
#include <limits.h>
#include <linux/errqueue.h>
#include <linux/if_packet.h>
#include <linux/ipv6.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <poll.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/rds.h>
#ifndef SO_EE_ORIGIN_ZEROCOPY
#define SO_EE_ORIGIN_ZEROCOPY       5
#endif
#ifndef SO_ZEROCOPY
#define SO_ZEROCOPY 60
#endif
#ifndef SO_EE_CODE_ZEROCOPY_COPIED
#define SO_EE_CODE_ZEROCOPY_COPIED  1
#endif
#ifndef MSG_ZEROCOPY
#define MSG_ZEROCOPY    0x4000000
#endif

#define TESTSIZE 16*1024
static char payload[TESTSIZE];
static long packets, bytes, completions, expected_completions;
static int  zerocopied = -1;
static uint32_t next_completion;

static void do_setsockopt(int fd, int level, int optname, int val)
{
    if (optname == SO_ZEROCOPY) {
        printf("set so_zerocopy\n");
    }
    if (setsockopt(fd, level, optname, &val, sizeof(val)))
        error(1, errno, "setsockopt %d.%d: %d", level, optname, val);
}

static bool do_sendmsg(int fd, struct msghdr *msg, bool do_zerocopy)
{
    int ret, len, flags;
    size_t i;
    len = 0;
    for (i = 0; i < msg->msg_iovlen; i++)
        len += msg->msg_iov[i].iov_len;
    flags = MSG_DONTWAIT;
    if (do_zerocopy) {
        printf("set msg_zerocopy\n");
        flags |= MSG_ZEROCOPY;
    }
    ret = sendmsg(fd, msg, flags);
    if (ret == -1 && errno == EAGAIN)
        return false;
    if (ret == -1)
        error(1, errno, "send");

    if (len) {
        packets++;
        bytes += ret;
        if (do_zerocopy && ret)
            expected_completions++;
    }
    return true;
}

static int do_setup_tx(int domain, int type, int protocol)
{
    int fd;
    fd = socket(domain, type, protocol);
    if (fd == -1)
        error(1, errno, "socket t");
    do_setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, 1);
    return fd;
}


static bool do_recv_completion(int fd)
{
    struct sock_extended_err *serr;
    struct msghdr msg = {};
    struct cmsghdr *cm;
    uint32_t hi, lo, range;
    int ret, zerocopy;
    char control[100];
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);
    ret = recvmsg(fd, &msg, MSG_ERRQUEUE);
    if (ret == -1 && errno == EAGAIN)
        return false;
    if (ret == -1)
        error(1, errno, "recvmsg notification");
    if (msg.msg_flags & MSG_CTRUNC)
        error(1, errno, "recvmsg notification: truncated");
    cm = CMSG_FIRSTHDR(&msg);
    if (!cm)
        error(1, 0, "cmsg: no cmsg");
    if (!((cm->cmsg_level == SOL_IP && cm->cmsg_type == IP_RECVERR) ||
          (cm->cmsg_level == SOL_IPV6 && cm->cmsg_type == IPV6_RECVERR) ||
          (cm->cmsg_level == SOL_PACKET && cm->cmsg_type == PACKET_TX_TIMESTAMP)))
        error(1, 0, "serr: wrong type: %d.%d",
              cm->cmsg_level, cm->cmsg_type);
    serr = (void *) CMSG_DATA(cm);
    if (serr->ee_origin != SO_EE_ORIGIN_ZEROCOPY)
        error(1, 0, "serr: wrong origin: %u", serr->ee_origin);
    if (serr->ee_errno != 0)
        error(1, 0, "serr: wrong error code: %u", serr->ee_errno);
    hi = serr->ee_data;
    lo = serr->ee_info;
    range = hi - lo + 1;
    /* Detect notification gaps. These should not happen often, if at all.
     * Gaps can occur due to drops, reordering and retransmissions.
     */
    if (lo != next_completion)
        fprintf(stderr, "gap: %u..%u does not append to %u\n",
            lo, hi, next_completion);
    next_completion = hi + 1;
    zerocopy = !(serr->ee_code & SO_EE_CODE_ZEROCOPY_COPIED);
    if (serr->ee_code == SO_EE_CODE_ZEROCOPY_COPIED) {
        printf("zerocopy is not valid, but why? It is rediculous!\n");
    }
    else {
        printf("zerocopy is available\n");
    }
    if (zerocopied == -1)
        zerocopied = zerocopy;
    else if (zerocopied != zerocopy) {
        fprintf(stderr, "serr: inconsistent\n");
        zerocopied = zerocopy;
    }

    completions += range;
    return true;
}


static void do_tx(int domain, int type, int protocol)
{
    struct iovec iov[3] = { {0} };
    struct msghdr msg = {0};
    int fd;
    fd = do_setup_tx(domain, type, protocol);
    struct sockaddr_in serv_addr;
    memset(&serv_addr, '0', sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000);
    inet_pton(AF_INET, "114.114.114.114", &serv_addr.sin_addr);
    connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    iov[0].iov_base = payload;
    iov[0].iov_len = sizeof(payload);
    msg.msg_iovlen++;
    msg.msg_iov = &iov[0];

    // printf("sendmsg\n");
    do_sendmsg(fd, &msg, true);
    // printf("wait notification\n");
    while(!do_recv_completion(fd));
    sleep(1);

    if (close(fd))
        error(1, errno, "close");
    fprintf(stderr, "tx=%lu (%lu B) txc=%lu zc=%c\n",
        packets, bytes, completions,
        zerocopied == 1 ? 'y' : 'n');
}

static void do_test(int domain, int type, int protocol)
{
    int i;
    for (i = 0; i < TESTSIZE; i++)
        payload[i] = 'a' + (i % 26);
    do_tx(domain, type, protocol);
}

int main()
{
    do_test(AF_INET, SOCK_DGRAM, 0);
    return 0;
}

person HypoGump    schedule 14.04.2020    source источник
comment
Я не знаком с нулевым копированием, но в документации, на которую вы ссылаетесь, говорится следующее: эта функция в настоящее время реализована для сокетов TCP.   -  person Felix G    schedule 14.04.2020
comment
lwn.net/Articles/773106 Linux уже поддерживает udp msg_zerocopy, если не поддерживает, setsockopt вернет ошибку неподдерживаемого протокола. Когда я использую Linux 4.14, я получаю эту ошибку, поэтому я обновляю ядро ​​​​до 5.4.   -  person HypoGump    schedule 14.04.2020
comment
При компиляции всегда включайте предупреждения, а затем исправьте эти предупреждения. Для gcc при минимальном использовании: -Wall -Wextra -Wconversion -pedantic -std=gnu11 Опубликованный код заставляет компилятор выводить несколько предупреждающих сообщений. Код, который выдает компилятор при выводе некоторых предупреждений, не обязательно является правильным кодом.   -  person user3629249    schedule 15.04.2020
comment
список включенных заголовочных файлов содержит несколько файлов, содержимое которых не используется. Включение файлов заголовков, содержимое которых не используется, является плохой практикой программирования.   -  person user3629249    schedule 15.04.2020
comment
Спасибо за совет, код взят из самотестирования ядра. Я просто удаляю некоторые неиспользуемые строки и использую их для проверки наличия или отсутствия ZEROCOPY.   -  person HypoGump    schedule 16.04.2020


Ответы (1)


После трассировки стека ядра я обнаружил, что skb_copy_ubufs приводит к результату, который вызывается dev_queue_xmit_nit. Это означает, что уведомление MSG_ZEROCOPY вернет SO_EE_CODE_ZEROCOPY_COPIED, если используются сетевые краны. В моем случае это dhclient и lldpd.service. После их убийства код исчез.

person HypoGump    schedule 02.05.2020