5
votes

According to the connect(2) man pages

If the socket sockfd is of type SOCK_DGRAM then serv_addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection to the socket that is bound to the address specified by serv_addr.

I am trying to filter packets from two different multicast groups that are being broadcasted on the same port and I thought connect() would have done the job but I can't make it work. In facts when I add it to my program I don't receive any packet. More info in this thread.

This is how I set the connect parameters:

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

4 Answers

12
votes

Your program (probably) has the following problems:

  • you should be using bind() instead of connect(), and
  • you're missing setsockopt(..., IP_ADD_MEMBERSHIP, ...).

Here's an example program that receives multicasts. It uses recvfrom(), not recv(), but it's the same except you also get the source address for each received packet.

To receive from multiple multicast groups, you have three options.

First option: Use a separate socket for each multicast group, and bind() each socket to a multicast address. This is the simplest option.

Second option: Use a separate socket for each multicast group, bind() each socket INADDR_ANY, and use a socket filter to filter out all but a single multicast group.

Because you've bound to INADDR_ANY, you may still get packets for other multicast groups. It is possible to filter them out using the kernel's socket filters:

#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));
}    

Third option: use a single socket to receive multicasts for all multicast groups.

In that case, you should do an IP_ADD_MEMBERSHIP for each of the groups. This way you get all packets on a single socket.

However, you need extra code to determine which multicast group a received packet was addressed to. To do that, you have to:

  • receive packets with recvmsg() and read the IP_PKTINFO or equivalent ancillary data message. However, to make recvmsg() give you this message, you first have to
  • enable reception of IP_PKTINFO ancillary data messages with setsockopt().

The exact thing you need to do depends on IP protocol version and OS. Here's how I did it (IPv6 code not tested): enabling PKTINFO and reading the option.

Here's a simple program that receives multicasts, which demonstrates the first option (bind to multicast address).

#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
votes

The first thing to note is that multicast packets are sent to a multicast address, not from a multicast address. connect() will allow (or not) packets received from a nominated address.

To configure your socket to receive multicast packets you need to use one of two socket options:

  • IP_ADD_MEMBERSHIP, or
  • IP_ADD_SOURCE_MEMBERSHIP

The former allows you to specify a multicast address, the latter allows you to specify a multicast address and source address of the sender.

This can be done using something like the following:


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 );

(error handling omitted for brevity)

To stop receiving multicast packets for this group address, use the socket options:

  • IP_DROP_MEMBERSHIP, or
  • IP_DROP_SOURCE_MEMBERSHIP

Note that a socket can have multiple multicast memberships. But, as the multicast address is the destination address of the packet, you need to be able to grab the destination address of the packet to be able to distinguish between packets for different multicast addresses.

To grab the destination address of the packet you'll need to use recvmsg() instead of recv() or recvfrom(). The destination address is contained within the IPPROTO_IP message level, of type DSTADDR_SOCKOPT. As @Ambroz Bizjak has stated, you'll need to set the IP_PKTINFO socket option to be able to read this information.


Other things to check are:

  • Is multicast supported in your kernel? Check for the existence of /proc/net/igmp to ensure it's been enabled.
  • Has multicast been enable on your network interface? Check for "MULTICAST" listed when you run ifconfig on your interface
  • Does your network interface support multicast? Historically not all have. If not you may be able to get around this by setting your interface to promiscuous mode. e.g. ifconfig eth0 promisc
0
votes

This should work as long as all the SENDING sockets are bound to the multicast address in question with bind. The address you specify in connect is matched against the SOURCE address of received packets, so you want to ensure that all packets have the same (multicast) SOURCE AND DESTINATION.

0
votes

bind(2) each socket to the address of the respective multicast group and port instead of INADDR_ANY. That would do the filtering for you.