我有一个UDP套接字绑定到INADDR_ANY来监听我的服务器上的所有IP数据包.我通过同一个套接字发送回复.
现在,当发送数据包时,服务器会自动选择将哪个IP用作源IP,但我希望能够自己设置传出源IP.
有没有办法做到这一点,而无需为每个IP创建一个单独的套接字?
尼古拉,使用单独的套接字并绑定(2)每个地址或弄乱路由表通常不是一个可行的选择,例如动态地址.单端IP_ADDRANY
UDP服务器应该能够响应接收到数据包的相同动态分配的IP地址.
幸运的是,还有另一种方式.根据系统的支持,您可以使用IP_PKTINFO
套接字选项来设置或接收有关消息的辅助数据.cmsg(3)
虽然comp.os.linux.development.system有一个特定的完整代码示例,但在很多地方都有在线覆盖辅助数据(via )IP_PKTINFO
.
链接中的代码使用IP_PKTINFO
(或IP_RECVDSTADDR
取决于平台)从辅助cmsg(3)
数据中获取UDP消息的目标地址.转述于此:
struct msghdr msg; struct cmsghdr *cmsg; struct in_addr addr; // after recvmsg(sd, &msg, flags); for(cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { addr = ((struct in_pktinfo*)CMSG_DATA(cmsg))->ipi_addr; printf("message received on address %s\n", inet_ntoa(addr)); } }
Gene,您的问题询问如何设置传出数据包的源地址.使用IP_PKTINFO
它可以设置传递给的辅助数据的ipi_spec_dst
字段.请参阅上面引用的帖子,以及有关如何在a中创建和操作辅助数据的指南.一个例子(这里不保证)可能是:struct in_pktinfo
sendmsg(2)
cmsg(3)
sendmsg(2)
struct msghdr
struct msghdr msg; struct cmsghdr *cmsg; struct in_pktinfo *pktinfo; // after initializing msghdr & control data to CMSG_SPACE(sizeof(struct in_pktinfo)) cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg); pktinfo->ipi_ifindex = src_interface_index; pktinfo->ipi_spec_dst = src_addr; // bytes_sent = sendmsg(sd, &msg, flags);
请注意,这与IPv6不同:struct in6_pktinfo::ipi6_addr
在recvmsg和sendmsg情况下都使用.
另请注意,Windows不支持in_pktinfo结构中与ipi_spec_dst等效的内容,因此您无法使用此方法在传出的winsock2数据包上设置源地址.
(引用的手册页 - 约1个超链接限制)
http:// linux.die.net/man/2/sendmsg http:// linux.die.net/man/3/cmsg
我想我会扩展Jeremy关于如何为IPv6做这件事.Jeremy遗漏了很多细节,一些文档(比如Linux的ipv6手册页)简直就是错误的.首先在一些发行版上你必须定义_GNU_SOURCE,否则一些IPv6内容没有定义:
#define _GNU_SOURCE #include#include #include
接下来以相当标准的方式设置套接字,以侦听特定UDP端口上的所有IP数据包(即IPv4和IPv6):
const int on=1, off=0; int result; struct sockaddr_in6 sin6; int soc; soc = socket(AF_INET6, SOCK_DGRAM, 0); setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); setsockopt(soc, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); setsockopt(soc, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); setsockopt(soc, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); memset(&sin6, '\0', sizeof(sin6)); sin6.sin6_family = htons(AF_INET6); sin6.sin6_port = htons(MY_UDP_PORT); result = bind(soc, (struct sockaddr*)&sin6, sizeof(sin6));
请注意,上面的代码为IPv6套接字设置了IP和IPv6选项.事实证明,如果数据包到达的IPv4地址,您将获得IP_PKTINFO(即IPv4)的CMSG的,即使它是IPv6套接字,如果你不启用他们,他们不会被发送.还要注意IPV6_RECPKTINFO选项被设置(未提到的人7的IPv6)而不是IPV6_PKTINFO(其在错误地描述人7的IPv6).现在收到一个udp包:
int bytes_received; struct sockaddr_in6 from; struct iovec iovec[1]; struct msghdr msg; char msg_control[1024]; char udp_packet[1500]; iovec[0].iov_base = udp_packet; iovec[0].iov_len = sizeof(udp_packet); msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = iovec; msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec); msg.msg_control = msg_control; msg.msg_controllen = sizeof(msg_control); msg.msg_flags = 0; bytes_received = recvmsg(soc, &msg, 0);
下一步是提取接口并解决从cmsg收到的UDP数据包:
struct in_pktinfo in_pktinfo; struct in6_pktinfo in6_pktinfo; int have_in_pktinfo = 0; int have_in6_pktinfo = 0; struct cmsghdr* cmsg; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { in_pktinfo = *(struct in_pktinfo*)CMSG_DATA(cmsg); have_in_pktinfo = 1; } if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { in6_pktinfo = *(struct in6_pktinfo*)CMSG_DATA(cmsg); have_in6_pktinfo = 1; } }
最后,我们使用相同的目的地返回响应.
int cmsg_space; iovec[0].iov_base = udp_response; iovec[0].iov_len = udp_response_length; msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = iovec; msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec); msg.msg_control = msg_control; msg.msg_controllen = sizeof(msg_control); msg.msg_flags = 0; cmsg_space = 0; cmsg = CMSG_FIRSTHDR(&msg); if (have_in6_pktinfo) { cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); *(struct in6_pktinfo*)CMSG_DATA(cmsg) = in6_pktinfo; cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo)); } if (have_in_pktinfo) { cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); *(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo; cmsg_space += CMSG_SPACE(sizeof(in_pktinfo)); } msg.msg_controllen = cmsg_space; ret = sendmsg(soc, &msg, 0);
再次注意,如果数据包是通过IPv4进入的,我们必须将一个IPv4选项放入cmsg中,即使它是一个AF_INET6套接字.至少,这就是你要为Linux做的事情.
这是一项令人惊讶的工作,但AFAICT是您在所有可想到的Linux环境中构建可用的强大UDP服务器所必须做的最小工作.TCP中不需要大部分内容,因为它透明地处理多宿主.