16

I have an application that is receiving data from multiple multicast sources on the same port. I am able to receive the data. However, I am trying to account for statistics of each group (i.e. msgs received, bytes received) and all the data is getting mixed up. Does anyone know how to solved this problem? If I try to look at the sender's address, it is not the multicast address, but rather the IP of the sending machine.

I am using the following socket options:

struct ip_mreq mreq;         
mreq.imr_multiaddr.s_addr = inet_addr("224.1.2.3");         
mreq.imr_interface.s_addr = INADDR_ANY;         
setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

and also:

setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
Cœur
  • 37,241
  • 25
  • 195
  • 267
Gigi
  • 271
  • 1
  • 4
  • 5

8 Answers8

11

After some years facing this linux strange behaviour, and using the bind workaround describe in previous answers, I realize that the ip(7) manpage describe a possible solution :

IP_MULTICAST_ALL (since Linux 2.6.31)
This option can be used to modify the delivery policy of multicast messages to sockets bound to the wildcard INADDR_ANY address. The argument is a boolean integer (defaults to 1). If set to 1, the socket will receive messages from all the groups that have been joined globally on the whole system. Otherwise, it will deliver messages only from the groups that have been explicitly joined (for example via the IP_ADD_MEMBERSHIP option) on this particular socket.

Then you can activate the filter to receive messages of joined groups using :

int mc_all = 0;
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_ALL, (void*) &mc_all, sizeof(mc_all))) < 0) {
    perror("setsockopt() failed");
}

This problem and the way to solve it enabling IP_MULTICAST_ALL is discussed in Redhat Bug 231899, this discussion contains test programs to reproduce the problem and to solve it.

mpromonet
  • 11,326
  • 43
  • 62
  • 91
  • This is not a solution to the described problem at all, because this method can not be used to separate multiple multicast streams sent to the same socket (same port is mentioned in the question). – Johannes Overmann Sep 20 '17 at 22:21
  • 1
    @JohannesOvermann : this option allow to receive on a socket only the multicast group subscribed on this socket, then this option answer to the question avoiding to receive all multicast group subscribed by others socket that share the same port (that could be in another process), the question does not mention that only one socket is use. One socket per multicast group allow this option to let the kernel makes the appropriate filter. – mpromonet Sep 20 '17 at 22:46
  • at mpromonet: No. The op explicitly says "multiple multicast sources on the same port". This means a single socket has to be used. You cannot really bind multiple sockets to the same port. Setting mc_all to 0 does not allow you to separate multicast streams sent to the same port. – Johannes Overmann Sep 21 '17 at 16:10
  • @JohannesOvermann I do disagree with you for severals reasons (1. in such a case setting SO_REUSEPORT is useless, 2. many answers suggest to bind to multicast group to avoid mix data, 3. duplicate question mention separated socket like [this](https://stackoverflow.com/questions/4957042/duplicate-packets-in-multicast-receiver-socket?noredirect=1&lq=1), 4."multiple multicast sources on the same port" doesnot say nothing about 1 socket for all groups or 1 socket per group, 5. setting mc_all to 0 allow to receive only group you explicitly joined ) – mpromonet Sep 21 '17 at 17:35
  • 1
    This is the best answer to this question, while DS's answer does the job, its much more cumbersome if you want to change which multicast group a socket is listening to, since you can't re-bind a socket, you have to completely close the socket and create a new one. – ScottG Oct 03 '19 at 14:02
  • 1
    Thank you! My problem is similar and your answer was perfect. If two programs on the same Linux machine subscribe to the same port number on different multicast groups, you will see cross talk. The subscriptions do not even have to be from the same process! Your answer stopped the cross talk and now I get only what I asked for. – Trade-Ideas Philip Aug 11 '20 at 19:03
10

[Edited to clarify that bind() may in fact include a multicast address.]

So the application is joining several multicast groups, and receiving messages sent to any of them, to the same port. SO_REUSEPORT allows you to bind several sockets to the same port. Besides the port, bind() needs an IP address. INADDR_ANY is a catch-all address, but an IP address may also be used, including a multicast one. In that case, only packets sent to that IP will be delivered to the socket. I.e. you can create several sockets, one for each multicast group. bind() each socket to the (group_addr, port), AND join group_addr. Then data addressed to different groups will show up on different sockets, and you'll be able to distinguish it that way.

I tested that the following works on FreeBSD:

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

int main(int argc, const char *argv[])
{
    const char *group = argv[1];

    int s = socket(AF_INET, SOCK_DGRAM, 0);
    int reuse = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == -1) {
        fprintf(stderr, "setsockopt: %d\n", errno);
        return 1;
    }

    /* construct a multicast address structure */
    struct sockaddr_in mc_addr;
    memset(&mc_addr, 0, sizeof(mc_addr));
    mc_addr.sin_family = AF_INET;
    mc_addr.sin_addr.s_addr = inet_addr(group);
    mc_addr.sin_port = htons(19283);

    if (bind(s, (struct sockaddr*) &mc_addr, sizeof(mc_addr)) == -1) {
        fprintf(stderr, "bind: %d\n", errno);
        return 1;
    }

    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(group);
    mreq.imr_interface.s_addr = INADDR_ANY;
    setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

    char buf[1024];
    int n = 0;
    while ((n = read(s, buf, 1024)) > 0) {
        printf("group %s fd %d len %d: %.*s\n", group, s, n, n, buf);
    }
}

If you run several such processes, for different multicast addresses, and send a message to one of the addresses, only the relevant process will receive it. Of course, in your case, you probably will want to have all the sockets in one process, and you'll have to use select or poll or equivalent to read them all.

DS.
  • 22,632
  • 6
  • 47
  • 54
  • I don't think you can bind to a multicast address - you need to bind to your local interface (INADDR_ANY). – Gigi Apr 30 '10 at 03:00
  • I can't test on linux, but I've written a little test (included in the edited answer), and verified that it works correctly on FreeBSD. – DS. May 02 '10 at 01:37
  • This is the classic workaround on linux that is used since a while. However since kernel 2.6.31, a socket option allow to filter on subscribed groups see answer http://stackoverflow.com/a/20919920/3102264 – mpromonet Jun 14 '14 at 15:53
5

Use setsockopt() and IP_PKTINFO or IP_RECVDSTADDR depending on your platform, assuming IPv4. This combined with recvmsg() or WSARecvMsg() allows you to find the source and destination address of every packet.

Unix/Linux, note FreeBSD uses IP_RECVDSTADDR whilst both support IP6_PKTINFO for IPv6.

Windows, also has IP_ORIGINAL_ARRIVAL_IF

Steve-o
  • 12,678
  • 2
  • 41
  • 60
3

Replace

mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);

with

mc_addr.sin_addr.s_addr = inet_addr (mc_addr_str);

it's help for me (linux), for each application i receive separate mcast stream from separate mcast group on one port.

Also you can look into VLC player source, it show many mcast iptv channel from different mcast group on one port, but i dont know, how it separetes channel.

Stephan
  • 41,764
  • 65
  • 238
  • 329
Helius
  • 61
  • 2
1

I have had to use multiple sockets each looking at different multicast group addresses, and then count statistics on each socket individually.

If there is a way to see the "receiver's address" as mentioned in the answer above, I can't figure it out.

One important point that also took me awhile - when I bound each of my individual sockets to a blank address like most python examples do:

sock[i].bind(('', MC_PORT[i])

I got all the multicast packets (from all multicast groups) on each socket, which didn't help. To fix this, I bound each socket to it's own multicast group

sock[i].bind((MC_GROUP[i], MC_PORT[i]))

And it then worked.

0

IIRC recvfrom() gives you a different read address/port for each sender.

You can also put a header in each packet identifying the source sender.

eile
  • 1,153
  • 6
  • 18
0

The Multicast address will be the receiver's address not sender's address in the packet. Look at the receiver's IP address.

Himanshu
  • 1,989
  • 16
  • 12
0

You can separate the multicast streams by looking at the destination IP addresses of the received packets (which will always be the multicast addresses). It is somewhat involved to do this:

Bind to INADDR_ANY and set the IP_PKTINFO socket option. You then have to use recvmsg() to receive your multicast UDP packets and to scan for the IP_PKTINFO control message. This gives you some side band information of the received UDP packet:

struct in_pktinfo {
    unsigned int   ipi_ifindex;  /* Interface index */
    struct in_addr ipi_spec_dst; /* Local address */
    struct in_addr ipi_addr;     /* Header Destination address */
};

Look at ipi_addr: This will be the multicast address of the UDP packet you just received. You can now handle the received packets specific for each multicast stream (multicast address) you are receiving.

Johannes Overmann
  • 4,914
  • 22
  • 38