recvfrom (2) получает UDP-трансляцию дважды, но tcpdump (8) получает его только один раз - PullRequest
1 голос
/ 09 мая 2019

Сводка : Я хочу получать пакеты от одного интерфейса, но setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface, 1 + strlen(iface)) плохо работает с recvfrom и показывает все пакеты на всех интерфейсах. tcpdump работает хорошо, однако.

У меня сильное чувство, что с программой-приемником что-то не так, но я не смог этого понять.


Я работаю с Netronome Agilio CX SmartNIC. Два порта на сетевой карте соединены вместе одним кабелем, а порт на материнской плате подключен к стене (так что я могу подключиться к нему по SSH). В ОС установлена ​​загружаемая плата NIC eth0, тогда как SmartNIC представляет два интерфейса: enp1s0np0 и enp1s0np1.

Поскольку два интерфейса на SmartNIC не имеют связанных IP-адресов, я должен отправить широковещательную рассылку на один порт, чтобы он поступил на другой порт. Сейчас я отправляю на enp1s0np0 и ожидаю от enp1s0np1.

Я также развернул программу разгрузки XDP, которая изменяет часть пакета, чтобы я мог знать, достигает ли пакет enp1s0np1. Программа изменяет строку в позиции 28 ~ 35 на другую строку (вида !......!).

Проблема, с которой я столкнулся, заключается в том, что я сам написал программу-получатель, и она получает два пакета для каждого отправляемого пакета - первый - это оригинал, а второй - пакет, модифицированный XDP. Однако tcpdump получает только модифицированный пакет (ожидаемое поведение).

Я не уверен, почему моя программа получает его дважды - я не думаю, что он должен видеть неизмененный пакет.

Это программа отправки пакетов. Он считывает 32 числа с плавающей запятой двойной точности и упаковывает их в 256-байтовый блок и добавляет в блок 16 байтов «магических чисел».

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "config.h"
#include "util.h"

typedef unsigned char byte;

void sanity_check(void);

int main(int argc, char** argv) {
    sanity_check();

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
        errorexit("socket");

    char iface[16] = "enp1s0np0";
    if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface, 1 + strlen(iface)))
        errorexit("setsockopt");
    int optval = -1;
    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, &optval, sizeof(int)))
        errorexit("setsockopt");

    struct sockaddr_in target_addr = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = 0xFFFFFFFF,
        .sin_port = htons(6666)
    };

    byte buf[272];
    // Prepare data
    {
        unsigned long magic = MAGIC;
        memcpy(buf + 0, &magic, sizeof magic);
        unsigned long zero = 0UL;
        memcpy(buf + 8, &zero, sizeof zero);
        double data;
        for (int i = 0; i < 32; i++) {
            scanf(" %lf", &data);
            memcpy(buf + 16 + 8 * i, &data, sizeof data);
        }
    }

    int sent = sendto(sock, buf, sizeof(buf), 0, (struct sockaddr*)&target_addr, sizeof(struct sockaddr));
    if (sent != 0)
        errorexit("send");
    printf("%d bytes sent.\n", sent);

    // if (shutdown(sock, SHUT_RDWR))
    if (close(sock))
        errorexit("close");

    return 0;
}

void sanity_check(void) {
    if (getuid() || geteuid()) {
        fprintf(stderr, "Need root to proceed\n");
        exit(1);
    }
}

Это программа-получатель. Фактически, он получает каждый пакет, который входит в компьютер, большинство из которых являются данными SSH. Мне пришлось добавить чеки на магический номер или он просто спамит терминал. Я думаю, что просто не удалось прослушать конкретный интерфейс. (Проверьте if (buf[28] != '!' || buf[35] != '!') continue;)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "config.h"
#include "util.h"

typedef unsigned char byte;

void sanity_check(void);

int main(int argc, char** argv) {
    sanity_check();

    int sock = socket(AF_PACKET, SOCK_DGRAM, htons(3));
    if (sock < 0)
        errorexit("socket");

    char iface[16] = "enp1s0np1";
    if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface, 1 + strlen(iface)))
        errorexit("setsockopt");

    struct sockaddr_in target_addr = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = htonl(INADDR_ANY),
        .sin_port = htons(UDP_PORT)
    };

    size_t bufsize = 8192;
    byte *buf = malloc(bufsize);
    unsigned long magic = MAGIC;
    int saddr_size = sizeof(struct sockaddr);
    printf("Preparing to receive... ");
    fflush(stdout);
    while (1) {
        int received = recvfrom(sock, buf, bufsize, 0,(struct sockaddr *)&target_addr , (socklen_t*)&saddr_size);
        if (received < 0)
            errorexit("receive");
        if (received == 0)
            break;
        else if (buf[28] != '!' || buf[35] != '!') // Magic check
            continue;
        printf("%d bytes received.\n", received);
        hexdump(buf, received);
    }

    if (close(sock))
        errorexit("close");

    free(buf);
    return 0;
}

void sanity_check(void) {
    if (getuid() || geteuid()) {
        fprintf(stderr, "Need root to proceed\n");
        exit(1);
    }
}

Файл util.c ( ссылка на Gist ) содержит две служебные функции (errorexit, которые являются просто оберткой perror и exit, и плохо обработанный вручную hexdump функция для отображения) и здесь не имеет значения.

Константа MAGIC определяется как

#define MAGIC 0x216C7174786A7A21UL // string "!zjxtql!"

Вот вывод на консоль моей программы (recv.c скомпилирован в recv) и команда tcpdump с усеченными нерелевантными данными. Перед тем, как обе программы будут уничтожены, из программы-отправителя отправляется только один пакет. Особо следует отметить, что данные в позиции 28 (изначально !zjxtql!, должны быть изменены на !wjfskb! программой разгрузки XDP).

$ sudo ./recv
Preparing to receive...
300 bytes received.
00000000  45 00 01 2C 4C 1A 40 00  40 11 B4 7F 72 D6 C6 51  |E..,L.@.@...r..Q|
00000010  FF FF FF FF B0 9F 1A 0A  01 18 3A 51 21 7A 6A 78  |..........:Q!zjx|
00000020  74 71 6C 21 00 00 00 00  00 00 00 00 29 5C 8F C2  |tql!........)\..|
00000120  14 AE F7 3F AE 47 E1 7A  14 AE F7 3F              |...?.G.z...?|
0000012C
300 bytes received.
00000000  45 00 01 2C 4C 1A 40 00  40 11 B4 7F 72 D6 C6 51  |E..,L.@.@...r..Q|
00000010  FF FF FF FF B0 9F 1A 0A  01 18 C0 9B 21 77 6A 66  |............!wjf|
00000020  73 6B 62 21 00 00 00 00  00 00 00 00 29 5C 8F C2  |skb!........)\..|
00000120  14 AE F7 3F AE 47 E1 7A  14 AE F7 3F              |...?.G.z...?|
0000012C
^C
$ sudo tcpdump -vv -X -i enp1s0np1 port 6666
tcpdump: listening on enp1s0np1, link-type EN10MB (Ethernet), capture size 262144 bytes
04:53:52.819657 IP (tos 0x0, ttl 64, id 8595, offset 0, flags [DF], proto UDP (17), length 300)
    agilio1415.47585 > 255.255.255.255.ircu-2: [bad udp cksum 0xb759 -> 0xc274!] UDP, length 272
        0x0000:  4500 012c 2193 4000 4011 df06 72d6 c651  E..,!.@.@...r..Q
        0x0010:  ffff ffff b9e1 1a0a 0118 b759 2177 6a66  ...........Y!wjf
        0x0020:  736b 6221 0000 0000 0000 0000 295c 8fc2  skb!........)\..
        0x0120:  14ae f73f ae47 e17a 14ae f73f            ...?.G.z...?
^C

Я попытался strace использовать tcpdump и попробовать его с помощью setsockopt:

sudo strace -e setsockopt tcpdump -vv -X -i enp1s0np1 port 6666

, что дает

setsockopt(3, SOL_PACKET, PACKET_ADD_MEMBERSHIP, {mr_ifindex=if_nametoindex("enp1s0np1"), mr_type=PACKET_MR_PROMISC, mr_alen=0, mr_address=}, 16) = 0
setsockopt(3, SOL_PACKET, PACKET_AUXDATA, [1], 4) = 0
setsockopt(3, SOL_PACKET, PACKET_VERSION, [1], 4) = 0
setsockopt(3, SOL_PACKET, PACKET_RESERVE, [4], 4) = 0
setsockopt(3, SOL_PACKET, PACKET_RX_RING, 0x7ffe9d8d1510, 28) = 0
setsockopt(7, SOL_SOCKET, SO_RCVBUF, [8388608], 4) = 0
setsockopt(7, SOL_SOCKET, SO_SNDBUF, [8388608], 4) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=1, filter=0x7fa5289de000}, 16) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=24, filter=0x56025631f280}, 16) = 0
tcpdump: listening on enp1s0np1, link-type EN10MB (Ethernet), capture size 262144 bytes

, поскольку я не понимаю остальных, я имитировал только первый вызов setsockopt tcpdump:

struct packet_mreq mreq = {
    .mr_ifindex = if_nametoindex(iface),
    .mr_type = PACKET_MR_PROMISC,
    .mr_alen = 0
};
if (setsockopt(sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)))
    errorexit("setsockopt");

Приведенный выше код заменяет вызов setsockopt(SO_BINDTODEVICE) в программе получателя, но я не заметил никакой разницы (все же все пакеты со всех интерфейсов перехватываются).

1 Ответ

0 голосов
/ 09 мая 2019

Похоже, все, что мне не хватает, это bind(2).К сожалению, SO_BINDTOINTERFACE не работает с AF_PACKET, поэтому bind(2) является единственным решением.

Код не сложен:

struct sockaddr_ll sll = {
    .sll_family = AF_PACKET,
    .sll_ifindex = if_nametoindex(iface),
    .sll_protocol = htons(3) // 3 = ETH_P_ALL
};
if (bind(sock, (struct sockaddr*)&sll, sizeof sll))
    errorexit("sock");

Из того же socket.7 page :

SO_BINDTOSOCKET

... Обратите внимание, что это работает только для некоторых типов сокетов, в частности, сокетов AF_INET. Он не поддерживается для сокетов пакетов (используйте обычный bind(2) там).

Хммм, думаю, мне следовало прочитать руководство более тщательно.

...