问题描述
我正在编写一个应用程序,它应该能够在套接字上接收 IPv4 或 IPv6 多播数据报。我编写了一个函数,可以通过 setsockopt
接收套接字的多播数据报(请参阅下面的代码)。我遇到的奇怪问题是 IPv4 案例 IP_ADD_MEMBERSHIP
的 setsockopt 有时因 errno No such device
而失败,而其他时候它按预期工作。我的应用程序在带有 raspbian 的 raspBerry pi 上运行。我非常感谢您的建议!
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,&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,sizeof(mreq)) != 0){
perror("Error setsockopt IPV6_JOIN_GROUP Failed");
exit(ERR_SETSOCKOPT_Failed);
}
}
return;
}
解决方法
基于与@dbush 的讨论,我希望通过以下方式改进代码:
- 使用新的推荐结构 ip_mreqn 而不是旧的结构 ip_mreq
- 使用 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);
}