Search code examples
csocketsunixmulticastmulticastsocket

Multicast loopback to sender socket


I am writing a C program which multicasts a packet. The same socket used for multicasting is subscribed to the multicast group on all interfaces. This would mean that the sender would receive is own packet back. I expect to the socket to receive the message twice, once on loopback interface and another on eth0 interface. Here is the code for the same.


#define MULTICAST_PORT 1112
#define MULTICAST_GROUP "239.0.0.1"

int create_multi_receiver()
{
    // Send is also through this socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in in_servaddr;
    struct ip_mreq mreq;
    // Bind UDP server
    bzero(&in_servaddr, sizeof(in_servaddr));
    in_servaddr.sin_family = AF_INET;
    in_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    in_servaddr.sin_port = htons(MULTICAST_PORT);
    if (bind(fd, (struct sockaddr *)&in_servaddr, sizeof(in_servaddr)) == -1)
        error_exit("bind error in UDP");

    mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_GROUP);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

    if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
        error_exit("setsockopt");

    int opt = 1;
    if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) < 0)
        error_exit("setsockfd");

    return fd;
}

struct msg *recv_multi_msg(int udpfd)
{
    struct msg nmb_msg;     /* space to msg into */
    struct iovec vector[1]; /* file name from the child */
    struct msghdr msg;      /* full message */
    struct cmsghdr *cmsg;   /* control message with the fd */

    /* set up the iovec for the file name */
    vector[0].iov_base = &nmb_msg;
    vector[0].iov_len = sizeof(nmb_msg);

    /* the message we're expecting to receive */
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = vector;
    msg.msg_iovlen = 1;

    /* overprovisioning buffer for now */
    char cmbuf[128];
    msg.msg_control = cmbuf;
    msg.msg_controllen = sizeof(cmbuf);
    printf("Receiving message..\n");
    if (recvmsg(udpfd, &msg, 0) == -1)
    {
        perror("recvmsg multireceiver");
        return NULL;
    }
    printf("received message..\n");
    // TODO: Test this part, very high chance of error
    for ( // iterate through all the control headers
        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        cmsg != NULL;
        cmsg = CMSG_NXTHDR(&msg, cmsg))
    {
        // ignore the control headers that don't match what we want
        if (cmsg->cmsg_level != IPPROTO_IP ||
            cmsg->cmsg_type != IP_PKTINFO)
        {
            continue;
        }
        struct in_pktinfo pi;
        memcpy(&pi, CMSG_DATA(cmsg), sizeof(pi));
        printf("Destination IP: %s\n", inet_ntoa(pi.ipi_spec_dst));
        // in_addr_t ip = (nmb_msg.mtype) >> 16;
        // if (pi.ipi_spec_dst.s_addr != ip)
        //     return NULL;

        // break;
    }
    return NULL;

        // Do some stuff later 
}



void send_multi_msg(int udpfd, int udsfd)
{
    struct msg msg;
    if (recv(udsfd, &msg, sizeof(msg), 0) > 0)
    {
        struct sockaddr_in addr;
        int alen;
        bzero(&addr, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(MULTICAST_GROUP);
        addr.sin_port = htons(MULTICAST_PORT);
        alen = sizeof(addr);
        printf("sending %s\n", msg.mtext);
        if (sendto(udpfd, &msg, sizeof(msg), 0, (struct sockaddr *)&addr, alen) == -1)
            perror("sendto in multicast");
    }
}

However, I am receiving the packet only eth0. Why is this so? IP_MULTICAST_LOOP is enabled by default.

I have tried checking StackOverflow but none of the questions have been answered satisfactorily.


Solution

  • When you send a multicast datagram with sendto, it only sends the packet out on a single interface. So you only receive one packet because you're only sending one packet.

    By default, the interface that sendto will use for multicast will typically be the first non-loopback network interface on the system. You can control which interface is used by setting the IP_MULTICAST_IF option on the sending socket. So if you want to send on multiple interfaces you'll need to call sendto multiple times and set IP_MULTICAST_IF before each send.

    You've also used INADDR_ANY as the interface to join the multicast group on. That will also typically be the first non-loopback interface. If you run your program and then while it's still up run netstat -ng you can see which interface was joined.

    If you want to listen for multicast on multiple interfaces, you need to set the IP_ADD_MEMBERSHIP option for each interface.