1

I writting an application that should be able to receive IPv4 or IPv6 multicast datagrams on a socket. I wrote a function that enables the reception of multicast datagrams for a socket through setsockopt (see code below). The strange problem that i am having is that the setsockopt for the IPv4 case IP_ADD_MEMBERSHIP is sometimes failing with the errno No such device and the other times it is working as expected. My application is running on a raspberry pi with raspbian. I really appreciate your suggestions!

void setRecvMulticastAddr(int *socketFD, struct sockaddr *addr, char *interface, char *multicastaddr){

    // Cast the sockaddr to a sockaddr storage struct
    struct sockaddr_storage *addrStorage = (struct sockaddr_storage *) addr;

    // Check if it is an IPv4 or IPv6 socket
    if(addrStorage->ss_family == AF_INET){

        // IPv4 multicast request
        struct ip_mreq mreq;

        // Convert the multicast IPv4 address string to an in_addr
        struct in_addr multiaddr;
        if(inet_pton(AF_INET, multicastaddr, &multiaddr) != 1){
            printf("Could not convert the IPv4 multicast address: %s", multicastaddr);
            exit(ERR_INETPTON_FAILED);
        }

        // Cast the sockaddr to a sockaddr_in struct
        struct sockaddr_in *addrin = (struct sockaddr_in *) addrStorage;

        // Fill out the IPv4 multicast request
        mreq.imr_interface = addrin->sin_addr;
        mreq.imr_multiaddr = multiaddr;

        // ### The setsockopt that fails sometimes: ###
        // Set the sockopt so the socket can receive on the multicast address
        if(setsockopt(*socketFD, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0){
            perror("Error setsockopt IP_ADD_MEMBERSHIP failed");
            exit(ERR_SETSOCKOPT_FAILED);
        }

    }else{

        // IPv6 multicast request
        struct ipv6_mreq mreq;

        // Set the interace name in the ifr
        struct ifreq ifr;
        snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface);

        // Get the ifrindex based on the interface name
        if(ioctl(*socketFD, SIOGIFINDEX, &ifr) < 0){
            printf("Could not get ifrindex: %s\n", strerror(errno));
        }

        // Convert the multicast addrress string to an in_addr6
        struct in6_addr multiaddr;
        if(inet_pton(AF_INET6, multicastaddr, &multiaddr) != 1){
            printf("Could not convert the IPv6 multicast address: %s", multicastaddr);
            exit(ERR_INETPTON_FAILED);
       }

       // Fill out the IPv6 multicast request
       mreq.ipv6mr_interface = ifr.ifr_ifindex;
       mreq.ipv6mr_multiaddr = multiaddr;

       // Set the sockopt so the socket can receive on the multicast address
       if(setsockopt(*socketFD, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) != 0){
            perror("Error setsockopt IPV6_JOIN_GROUP failed");
            exit(ERR_SETSOCKOPT_FAILED);
       }

    }

    return;
}
user207421
  • 305,947
  • 44
  • 307
  • 483
mab0189
  • 126
  • 12
  • 2
    Try printing the value of `addrin->sin_addr` and verify that it's set to an active IP address on an active local interface. – dbush Apr 05 '21 at 19:28
  • @dbush I am setting up the socket that should be able to receive the multicast datagram messages with getaddrinfo and the AI_PASSIVE flag in the hints and pass it in the addr argument. I printed the value and got `sin_addr is: 0.0.0.0` as expected. This time it worked on the first try as you would expect. – mab0189 Apr 05 '21 at 22:39
  • 1
    Using 0.0.0.0 as the interface address causes the system to join on the "default" interface. It could be that that interface is down. If you have virtual machines or docker running on the host it might be choosing one of those interfaces. Try joining a specific interface. – dbush Apr 05 '21 at 22:43
  • @dbush I read the [manpage](https://man7.org/linux/man-pages/man7/ip.7.html) again and i think i should use `ip_mreqn` struct since `ip_mreq` is the old struct for this purpose. It should work but better be save. Maybe i also should consider figuring out the local ip address of the interface with ioctl rather than using the sockaddr of the socket that is on `0.0.0.0`. – mab0189 Apr 05 '21 at 22:54
  • @dbush thank you very very much for the great input! I think using `0.0.0.0` as the interface might have caused the problem. I remember that one time the socket joined the multicast group on the interface `wlan0` and not `eth0` like i wanted. This might also explain why it worked on IPv6 since its argument is only dependend on the interface name therefore it joins always on the right interface. I will try it out tomorrow. Thank you very much! – mab0189 Apr 05 '21 at 23:03

1 Answers1

1

Based on the discussion with @dbush i hopefully improved the code by:

  1. Using the new recommended struct ip_mreqn instead of the old struct ip_mreq
  2. Determining the address of the interface with ioctl rather than using the sockaddr

Here is the new code for the IPv4 case:

    // IPv4 multicast request
    struct ip_mreqn mreqn;

    // Set the interace family and name in the ifr
    struct ifreq ifr;
    ifr.ifr_addr.sa_family = AF_INET;
    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface);

    // Get the ifrindex of the interface
    if(ioctl(*socketFD, SIOGIFINDEX, &ifr) < 0){
        printf("Error could not get the interface index: %s\n", strerror(errno));
        close(*socketFD);
        exit(ERR_IOCTL_INDEX);
    }

    mreqn.imr_ifindex = ifr.ifr_ifindex;

    // Get the IP address of the interface
    if(ioctl(*socketFD, SIOCGIFADDR, &ifr) != 0){
        printf("Error could not get the interface address: %s\n", strerror(errno));
        close(*socketFD);
        exit(ERR_IOCTL_ADDR);
    }

    // Cast the sockaddr to a sockaddr_in to get the in_addr value
    struct sockaddr_in *ifaddr = (struct sockaddr_in*) &ifr.ifr_addr;
    mreqn.imr_address = ifaddr->sin_addr;

    // Convert the multicast IPv4 address string to an in_addr
    struct in_addr multiaddr;
    if(inet_pton(AF_INET, multicastaddr, &multiaddr) != 1){
        printf("Error could not convert the IPv4 multicast address: %s", multicastaddr);
        exit(ERR_INETPTON_FAILED);
    }

    mreqn.imr_multiaddr = multiaddr;

    // Set the sockopt so the socket can receive on the multicast address
    if(setsockopt(*socketFD, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)) != 0){
        perror("Error setsockopt IP_ADD_MEMBERSHIP failed");
        exit(ERR_SETSOCKOPT_FAILED);
    }
mab0189
  • 126
  • 12
  • 1
    This looks good, although I don't think you need to set both `imr_ifindex` and `imr_address`. I've always just used `struct ip_mreq`. – dbush Apr 06 '21 at 11:51
  • @dbush Thank you very much for your support. I also wondered why the `struct ip_mreqn` needs both the index and the address of the interface but the struct for the IPv6 only needs the index. Because it's already in the code and the manpage does not mention that only one them is enough, I'll keep it just to be safe. – mab0189 Apr 06 '21 at 16:10