Вопрос для сокета UDP Unicast/Multicast через VLAN

Я новичок в VLAN и кодирую программу UDP для отправки/получения одноадресной и многоадресной рассылки через VLAN. Мне нужно получить три вещи из входящей посылки.

  1. IP-адрес и порт отправителя (чтобы я мог записать, кто их отправил).
  2. IP-адрес и порт получателя (чтобы я мог узнать, что это исходит от одноадресной или многоадресной рассылки).
  3. Конечно, сами данные.

С моими кодами (перечисленными ниже) многоадресная рассылка выглядит хорошо, но у одноадресной есть проблемы.

// gcc udp.c -o udp -pthread

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>
#include <unistd.h>

#include <pthread.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>

static int fd = 0;
static uint16_t port = 0;
static char* local = NULL;
static char* unicast = NULL;
static char* multicast = NULL;

static void* sending(void *_arg)
{
    int i = 0;
    struct sockaddr_in target;
    while(1) {
        memset((char *) &target, 0, sizeof(target));
        target.sin_family = AF_INET;
        if((i % 2) == 1) {
            target.sin_addr.s_addr = inet_addr(unicast);
        }
        else {
            target.sin_addr.s_addr = inet_addr(multicast);
        }
        target.sin_port = htons(port);

        char d[1] = {i++};
        sendto(fd, d, 1, 0, (struct sockaddr*)&target, sizeof(target));

        sleep(1);
    }

    return (void*)0;
}

int main(int argc, char const *argv[])
{
    port = atoi(argv[1]);
    local = strdup(argv[2]);
    unicast = strdup(argv[3]);
    multicast = strdup(argv[4]);

    fd = socket(AF_INET, SOCK_DGRAM, 0);

    int on = 1, off = 0;

    setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));

    struct ip_mreq group;
    memset(&group, 0, sizeof(struct ip_mreq));
    group.imr_multiaddr.s_addr = inet_addr(multicast);
    group.imr_interface.s_addr = inet_addr(local);
    setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
    setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));
    setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &off, sizeof(off));
    setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

    struct sockaddr_in localSock;
    memset((char *)&localSock, 0, sizeof(localSock));
    localSock.sin_family = AF_INET;
    localSock.sin_port = htons(port);
    localSock.sin_addr.s_addr = INADDR_ANY;           /* KEY CODE 1 */
    // localSock.sin_addr.s_addr = inet_addr(local);  /* KEY CODE 2 */
    bind(fd, (struct sockaddr*)&localSock, sizeof(localSock));

    pthread_t tid;
    pthread_create(&tid, NULL, sending, NULL);

    const int max_length = 2048;
    char data_[2048];
    while(1) {
        char cmbuf[BUFSIZ];
        struct msghdr msg;
        struct iovec iov[2];
        (void)memset(&msg, 0, sizeof(msg));
        (void)memset(&iov, 0, sizeof(iov));

        iov[0].iov_base = data_;
        iov[0].iov_len = max_length-1;

        struct sockaddr_in addr;
        msg.msg_name = &addr;
        msg.msg_namelen = (int)(sizeof(addr));
        msg.msg_iov = iov;
        msg.msg_iovlen = (int)(1);
        msg.msg_control = (caddr_t)cmbuf;
        msg.msg_controllen = sizeof(cmbuf);

        int result = recvmsg(fd, &msg, 0);
        if(result > 0) {
            printf("[%s:%d->", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
            for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                                 cmsg != NULL;
                                 cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                if (cmsg->cmsg_level == IPPROTO_IP
                 && cmsg->cmsg_type == IP_PKTINFO) {
                    struct in_pktinfo *pi =
                                (struct in_pktinfo *) CMSG_DATA(cmsg);
                    if (pi) {
                        printf("%15s:%d]", inet_ntoa(pi->ipi_addr),
                                           ntohs(addr.sin_port));
                    }
                }
            }
            printf("%02X\n", (uint8_t)data_[0]);
        }
    }

    // release resources ....
    return 0;
}

Когда я запускаю его на двух ПК, я обнаруживаю, что адрес отправителя одноадресной рассылки неверен.

ПК1:

4: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 42:2a:e6:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 172.0.0.1/16 brd 172.0.255.255 scope global tap0
       valid_lft forever preferred_lft forever
5: tap0.6@tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 42:2a:e6:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 172.0.0.6/16 brd 172.0.255.255 scope global tap0.6
       valid_lft forever preferred_lft forever

./udp 30490 172.0.0.6 172.0.0.106 224.244.224.245
[172.0.0.100:30490->      172.0.0.6:30490]03
[172.0.0.106:30490->224.244.224.245:30490]04
[172.0.0.100:30490->      172.0.0.6:30490]05
[172.0.0.106:30490->224.244.224.245:30490]06

ПК2:

3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 172.0.0.100/16 brd 172.0.255.255 scope global enp0s8
       valid_lft forever preferred_lft forever
4: enp0s8.6@enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 08:00:27:faxxxx:xx brd ff:ff:ff:ff:ff:ff
    inet 172.0.0.106/16 brd 172.0.255.255 scope global enp0s8.6
       valid_lft forever preferred_lft forever

./udp 30490 172.0.0.106 172.0.0.6 224.244.224.245
[172.0.0.6:30490->224.244.224.245:30490]00
[172.0.0.1:30490->    172.0.0.106:30490]01
[172.0.0.6:30490->224.244.224.245:30490]02
[172.0.0.1:30490->    172.0.0.106:30490]03

Мы видим, что адрес отправителя одноадресной рассылки не является IP-адресом VLAN.

Затем я попытался привязать сокет к IP-адресу локальной VLAN. Значит, я удалил коды, где помечены как KEY CODE 1, и добавил коды, где помечены как KEY CODE 2. После этого я больше не мог принимать мультикасты. Но в этот момент я мог поймать эти мультикаст-пакеты на tcpdump.

17:21:37.682577 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:38.550628 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:38.682709 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:39.550925 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:39.682835 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:40.551344 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:40.682982 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:41.551612 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:41.683123 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1

Похоже, когда я привязываю сокет к локальному IP-адресу VLAN, recvmsg больше не работает для многоадресной рассылки.

Итак, есть идеи, что мне не хватает в кодах?


person zeerd    schedule 27.11.2020    source источник


Ответы (1)


На обеих машинах физический интерфейс и виртуальный интерфейс имеют IP-адреса в одной и той же подсети, поэтому одноадресный пакет может быть отправлен в зависимости от того, как настроена ваша таблица маршрутизации (запустите netstat -nr, чтобы просмотреть таблицу маршрутизации). На основе вывода кажется, что таблица маршрутизации отдает предпочтение физическому интерфейсу, а не виртуальному интерфейсу на обеих машинах.

Когда вы привязываете сокет к определенному сетевому интерфейсу в Linux, вы не сможете получать многоадресные пакеты на этот сокет, даже если вы присоединились к группе многоадресной рассылки. Вы можете привязаться к многоадресному адресу, но тогда вы не будете получать одноадресные пакеты. Поэтому, если вы хотите получать одноадресные и многоадресные пакеты в одном сокете, вам необходимо выполнить привязку к INADDR_ANY.

Вам потребуется либо изменить таблицу маршрутизации (с помощью команды route или ip route), либо изменить виртуальные интерфейсы, чтобы использовать IP-адреса в другой подсети, чем физические интерфейсы.

person dbush    schedule 27.11.2020
comment
Большое Вам спасибо. Я изменил IP-адреса VLAN (на 172.0.6.10/172.0.6.100) и добавил новое правило маршрутизации (route add -net 172.0.6.0 netmask 255.255.255.0 dev $if.6). Теперь это работает с моим ожиданием. - person zeerd; 30.11.2020