IPv6 UDP组播设默认接口0时connect失败sendto正常问题
问题描述
当前尝试搭建基于组播的IPv6客户端/服务端UDP通信能力。
connect + send 场景
故障出现在IPv6 UDP组播套接字的connect调用环节:
- (a) 若通过
setsockopt配置IPV6_MULTICAST_IF选项选择默认接口0,connect调用会执行失败。 - (b) 若选择的接口索引对应系统中实际存在的活跃组播接口(例如通过
if_nametoindex("eth0")获取的索引值),connect调用可成功执行。
sendto 场景
若不对目标地址提前调用connect,直接使用sendto发送数据,两种配置均可成功:无论是选择默认接口0的场景(a),还是通过if_nametoindex指定实际接口的场景(b)。
复现示例
下方示例代码覆盖了客户端向组播地址发送数据的四种运行方式,编译运行结果如下:
- connect + send (a):
./main 1- 执行失败,报错:Invalid argument - connect + send (b):
./main 1 eth0- 执行成功 - sendto (a):
./main 0- 执行成功 - sendto (b):
./main 0 eth0- 执行成功
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdlib.h> #include <arpa/inet.h> #define MAXBUF 65536 #define ADDR "ff02::1:5" #define PORT "50000" int client(int use_connect_and_send, int interface_index) { int sock; int status; struct addrinfo sainfo, *psinfo, *linfo; struct sockaddr_in6 sin6; int sin6len; char buffer[MAXBUF]; sprintf(buffer, "hello"); sin6len = sizeof(struct sockaddr_in6); sock = socket(AF_INET6, SOCK_DGRAM, 0); memset(&sin6, 0, sizeof(struct sockaddr_in6)); sin6.sin6_port = htons(0); sin6.sin6_family = AF_INET6; sin6.sin6_addr = in6addr_any; memset(&sainfo, 0, sizeof(struct addrinfo)); memset(&sin6, 0, sin6len); sainfo.ai_flags = 0; sainfo.ai_family = AF_UNSPEC; sainfo.ai_socktype = SOCK_DGRAM; sainfo.ai_protocol = IPPROTO_UDP; status = getaddrinfo(ADDR, PORT, &sainfo, &psinfo); struct ipv6_mreq ipv6; ipv6.ipv6mr_interface = interface_index; if((setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const void *) &ipv6.ipv6mr_interface, sizeof(ipv6.ipv6mr_interface))) < 0) { perror("Setting local interface error"); } if (use_connect_and_send) { status = connect(sock, (struct sockaddr *)psinfo->ai_addr, sin6len); if(status != 0) { perror("connect failed"); exit(EXIT_FAILURE); } status = send(sock, buffer, strlen(buffer), 0); } else { status = sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr *)psinfo->ai_addr, sin6len); } if (status < 0) { perror("send failed"); } printf("buffer : %s \t%d\n", buffer, status); // free memory freeaddrinfo(psinfo); psinfo = NULL; shutdown(sock, 2); close(sock); return 0; } int main(int argc, char* argv[]) { if(argc < 2) { printf("specify wheter or not to use sendto (0) or connect+send (1)\n"); exit(EXIT_FAILURE); } int use_connect_and_send = atoi(argv[1]); int interface_index = 0; if(argc > 2) { interface_index = if_nametoindex(argv[2]); if (interface_index == 0) { perror("nametoindex failed, does the given interface exist?"); } } client(use_connect_and_send, interface_index); }
咨询问题
请问在**connect + send (a)**场景下存在什么配置错误?为何选择默认接口时sendto可成功执行,connect的行为却存在差异?
原因说明
这不是配置错误,是系统内核对UDP套接字connect和sendto两个系统调用的处理逻辑差异导致的:
- 对UDP套接字调用
connect时,内核不仅会记录目标地址,还会提前做路由可达性校验,必须找到明确的出口接口才能返回成功。当把IPV6_MULTICAST_IF设为0(代表使用默认接口)时,connect阶段内核不会触发组播默认接口的自动选择流程,此时找不到匹配组播目标地址的路由项,直接返回Invalid argument错误。 - 调用
sendto时,内核不会提前做路由校验,路由解析是在实际发送报文的环节才执行的。这时候如果IPV6_MULTICAST_IF为0,内核会自动按照系统的组播路由规则选择默认出口接口,流程可以正常走通,因此不会报错。
处理方案
如果要在IPv6 UDP组播场景下使用connect+send的调用方式,不要把IPV6_MULTICAST_IF设为0,必须显式指定实际存在的活跃接口索引,也就是场景(b)中的实现方式即可。
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

