UDP connect () и recv () в Linux - PullRequest
       3

UDP connect () и recv () в Linux

5 голосов
/ 13 декабря 2011

Согласно страницам руководства connect (2)

Если сокет sockfd имеет тип SOCK_DGRAM, то serv_addr - это адрес, по которому дейтаграммы отправляются по умолчанию, и единственный адрес, с которого принимаются дейтаграммы . Если сокет имеет тип SOCK_STREAM или SOCK_SEQPACKET, этот вызов пытается установить соединение с сокетом, который привязан к адресу, указанному в serv_addr.

Я пытаюсь отфильтровать пакеты из двух разных групп многоадресной рассылки, которые транслируются на одном и том же порту, и я подумал, что connect () справился бы с этой задачей, но не могу заставить ее работать. Фактически, когда я добавляю его в свою программу, я не получаю никакого пакета. Больше информации в этой теме .

Вот как я устанавливаю параметры подключения:

memset(&mc_addr, 0, sizeof(mc_addr));
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr(multicast_addr);
mc_addr.sin_port = htons(multicast_port);
printf("Connecting...\n");
if( connect(sd, (struct sockaddr*)&mc_addr, sizeof(mc_addr)) < 0 ) {
  perror("connect");
  return -1;
}

printf("Receiving...\n");
while( (len = recv(sd, msg_buf, sizeof(msg_buf), 0)) > 0 )
  printf("Received %d bytes\n", len);

Ответы [ 4 ]

12 голосов
/ 13 декабря 2011

Ваша программа (вероятно) имеет следующие проблемы:

  • вы должны использовать bind () вместо connect (), а
  • вам не хватает setsockopt (..., IP_ADD_MEMBERSHIP, ...).

Вот пример программы, которая получает многоадресную рассылку.Он использует recvfrom (), а не recv (), но он такой же, за исключением того, что вы также получаете адрес источника для каждого полученного пакета.

Для получения из нескольких групп многоадресной рассылки у вас есть три опции.

Первый параметр : использовать отдельный сокет для каждой группы многоадресной рассылки и связывать () каждый сокет с адресом многоадресной рассылки .Это самый простой вариант.

Второй параметр : использовать отдельный сокет для каждой группы многоадресной рассылки, связывать () каждый сокет INADDR_ANY и использовать фильтр сокетов для фильтрации всех, кроме одной многоадресной рассылки.group.

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

#include <stdint.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/filter.h>

/**
 * Adds a Linux socket filter to a socket so that only IP
 * packets with the given destination IP address will pass.
 * dst_addr is in network byte order.
 */
int add_ip_dst_filter (int fd, uint32_t dst_addr)
{
    uint16_t hi = ntohl(dst_addr) >> 16;
    uint16_t lo = ntohl(dst_addr) & 0xFFFF;

    struct sock_filter filter[] = {
        BPF_STMT(BPF_LD + BPF_H + BPF_ABS, SKF_NET_OFF + 16), // A <- IP dst high
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, hi, 0, 3),        // if A != hi, goto ignore
        BPF_STMT(BPF_LD + BPF_H + BPF_ABS, SKF_NET_OFF + 18), // A <- IP dst low
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, lo, 0, 1),        // if A != lo, goto ignore
        BPF_STMT(BPF_RET + BPF_K, 65535),                     // accept
        BPF_STMT(BPF_RET + BPF_K, 0)                          // ignore
    };

    struct sock_fprog fprog = {
        .len = sizeof(filter) / sizeof(filter[0]),
        .filter = filter
    };

    return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
}    

Третий вариант : использовать один сокет для получения многоадресных рассылок для всех групп многоадресной рассылки.

InВ этом случае вы должны сделать IP_ADD_MEMBERSHIP для каждой из групп.Таким образом вы получаете все пакеты в одном сокете.

Однако вам необходим дополнительный код, чтобы определить, к какой группе многоадресной рассылки был адресован полученный пакет.Для этого вам необходимо:

  • получить пакеты с помощью recvmsg () и прочитать IP_PKTINFO или эквивалентное сообщение вспомогательных данных.Тем не менее, чтобы recvmsg () выдал вам это сообщение, сначала вы должны
  • включить прием сообщений вспомогательных данных IP_PKTINFO с помощью setsockopt ().

Именно то, что вам нужно сделатьзависит от версии протокола IP и ОС.Вот как я это сделал (код IPv6 не тестировался): включение PKTINFO и с чтением опции .

Вот простая программа, которая получает многоадресные рассылки, которая демонстрирует первый вариант(привязать к многоадресному адресу).

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXBUFSIZE 65536

int main (int argc, char **argv)
{
    if (argc != 4) {
        printf("Usage: %s <group address> <port> <interface address>\n", argv[0]);
        return 1;
    }

    int sock, status, socklen;
    char buffer[MAXBUFSIZE+1];
    struct sockaddr_in saddr;
    struct ip_mreq imreq;

    // set content of struct saddr and imreq to zero
    memset(&saddr, 0, sizeof(struct sockaddr_in));
    memset(&imreq, 0, sizeof(struct ip_mreq));

    // open a UDP socket
    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket failed!");
        return 1;
    }

    // join group
    imreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
    imreq.imr_interface.s_addr = inet_addr(argv[3]);
    status = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
    (const void *)&imreq, sizeof(struct ip_mreq));

    saddr.sin_family = PF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    status = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
    if (status < 0) {
        perror("bind failed!");
        return 1;
    }

    // receive packets from socket
    while (1) {
        socklen = sizeof(saddr);
        status = recvfrom(sock, buffer, MAXBUFSIZE, 0, (struct sockaddr *)&saddr, &socklen);
        if (status < 0) {
            printf("recvfrom failed!\n");
            return 1;
        }

        buffer[status] = '\0';
        printf("Received: '%s'\n", buffer);
    }
}
1 голос
/ 13 декабря 2011

Первое, что следует отметить, это то, что многоадресные пакеты отправляются на адрес многоадресной рассылки, а не с адрес многоадресной рассылки. connect () разрешает (или нет) пакеты, полученные с назначенного адреса.

Чтобы настроить сокет для приема многоадресных пакетов, вам необходимо использовать один из двух параметров сокета:

  • IP_ADD_MEMBERSHIP или
  • IP_ADD_SOURCE_MEMBERSHIP

Первый позволяет указывать адрес многоадресной рассылки, второй - указывать адрес многоадресной рассылки и адрес отправителя.

Это можно сделать, используя что-то вроде следующего:


struct ip_mreq groupJoinStruct;
unsigned long groupAddr = inet_addr("239.255.0.1");

groupJoinStruct.imr_multiaddr.s_addr = groupAddr;
groupJoinStruct.imr_interface.s_addr = INADDR_ANY;   // or the address of a specific network interface
setsockopt( yourSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &groupJoinStruct );

(обработка ошибок для краткости опущена)

Чтобы прекратить прием многоадресных пакетов для этого группового адреса, используйте параметры сокета:

  • IP_DROP_MEMBERSHIP или
  • IP_DROP_SOURCE_MEMBERSHIP

Обратите внимание, что сокет может иметь несколько многоадресных членств. Но так как адрес многоадресной рассылки является адресом назначения пакета, вам необходимо иметь возможность получить адрес получателя пакета, чтобы иметь возможность различать пакеты для разных адресов многоадресной рассылки.

Чтобы получить адрес получателя пакета, вам нужно будет использовать recvmsg() вместо recv() или recvfrom(). Адрес назначения содержится на уровне сообщения IPPROTO_IP типа DSTADDR_SOCKOPT. Как сказал @Ambroz Bizjak, вам нужно установить опцию сокета IP_PKTINFO, чтобы иметь возможность читать эту информацию.


Другие вещи, которые нужно проверить:

  • Многоадресная рассылка поддерживается в вашем ядре? Проверьте наличие / proc / net / igmp и убедитесь, что он включен.
  • Многоадресная рассылка была включена на вашем сетевом интерфейсе? Проверьте наличие «MULTICAST» в списке при запуске ifconfig на вашем интерфейсе
  • Ваш сетевой интерфейс поддерживает многоадресную передачу? Исторически не у всех есть. Если нет, вы можете обойти это, установив интерфейс в случайный режим. например ifconfig eth0 promisc
0 голосов
/ 13 декабря 2011

bind(2) каждый сокет с адресом соответствующей многоадресной группы и порта вместо INADDR_ANY. Это сделало бы фильтрацию за вас.

0 голосов
/ 13 декабря 2011

Это должно работать до тех пор, пока все сокеты SENDING привязаны к рассматриваемому многоадресному адресу с помощью bind. Адрес, указанный в connect, сопоставляется с адресом SOURCE полученных пакетов, поэтому необходимо убедиться, что все пакеты имеют одинаковый (многоадресный) ИСТОЧНИК И НАЗНАЧЕНИЕ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...