【问题标题】:Error setsockopt IP_ADD_MEMBERSHIP: No such device错误 setsockopt IP_ADD_MEMBERSHIP:没有这样的设备
【发布时间】:2021-07-01 15:29:15
【问题描述】:

我正在编写一个应用程序,它应该能够在套接字上接收 IPv4 或 IPv6 多播数据报。我编写了一个函数,可以通过setsockopt 接收套接字的多播数据报(见下面的代码)。我遇到的一个奇怪问题是 IPv4 案例 IP_ADD_MEMBERSHIP 的 setsockopt 有时会因 errno No such device 而失败,而其他时候它会按预期工作。我的应用程序在带有 raspbian 的树莓派上运行。非常感谢您的建议!

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

【问题讨论】:

  • 尝试打印 addrin-&gt;sin_addr 的值并验证它是否设置为活动本地接口上的活动 IP 地址。
  • @dbush 我正在设置套接字,该套接字应该能够接收带有 getaddrinfo 和提示中的 AI_PASSIVE 标志的多播数据报消息,并将其传递到 addr 参数中。我打印了值并按预期​​得到了sin_addr is: 0.0.0.0。这一次,它在第一次尝试时就如您所愿。
  • 使用 0.0.0.0 作为接口地址会导致系统加入“默认”接口。可能是该接口已关闭。如果您在主机上运行虚拟机或 docker,它可能会选择其中一个接口。尝试加入特定界面。
  • @dbush 我再次阅读了manpage,我认为我应该使用ip_mreqn 结构,因为ip_mreq 是用于此目的的旧结构。它应该可以工作,但最好保存。也许我还应该考虑使用 ioctl 找出接口的本地 IP 地址,而不是使用0.0.0.0 上的套接字的 sockaddr。
  • @dbush 非常感谢您的宝贵意见!我认为使用0.0.0.0 作为接口可能会导致问题。我记得有一次套接字在接口wlan0 上加入了多播组,而不是像我想要的那样eth0。这也可以解释为什么它在 IPv6 上工作,因为它的参数只依赖于接口名称,因此它总是连接到正确的接口上。我明天试试。非常感谢!

标签: c sockets multicast ipv4 setsockopt


【解决方案1】:

根据与@dbush 的讨论,我希望通过以下方式改进代码:

  1. 使用推荐的新结构 ip_mreqn 代替旧结构 ip_mreq
  2. 使用 ioctl 而不是使用 sockaddr 确定接口的地址

这是 IPv4 案例的新代码:

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

【讨论】:

  • 这看起来不错,尽管我认为您不需要同时设置 imr_ifindeximr_address。我一直只用struct ip_mreq
  • @dbush 非常感谢您的支持。我还想知道为什么struct ip_mreqn 需要接口的索引和地址,而 IPv6 的结构只需要索引。因为它已经在代码中,并且手册页没有提到只有一个就足够了,所以为了安全起见,我会保留它。
猜你喜欢
  • 2017-03-07
  • 2014-07-03
  • 1970-01-01
  • 1970-01-01
  • 2015-06-08
  • 1970-01-01
  • 2021-11-14
  • 2017-02-01
  • 1970-01-01
相关资源
最近更新 更多