如何支持IPv4和IPv6连接

70

我目前正在开发一个UDP套接字应用程序,需要添加支持,以便IPV4和IPV6连接可以向服务器发送数据包。

我希望有人能帮助我指明方向,因为我找到的大多数文档都不完整。如果您能指出Winsock和BSD套接字之间的任何区别,那将非常有帮助。

提前感谢您的帮助!

5个回答

98

最好的方法是创建一个IPv6服务器套接字,还可以接受IPv4连接。为此,创建一个普通的IPv6套接字,关闭套接字选项IPV6_V6ONLY,将其绑定到“任何”地址,并开始接收。 IPv4地址将以IPv4-mapped格式表示为IPv6地址。

系统之间的主要区别在于IPV6_V6ONLY选项是否可用,以及默认情况下它是否被打开或关闭。在Linux上,默认情况下它被关闭(即允许双栈套接字而不需要setsockopt),而在大多数其他系统上它被打开。

此外,Windows XP上的IPv6堆栈不支持该选项。在这些情况下,您需要创建两个单独的服务器套接字,并将它们放入select中或多线程中。


9
声称在Linux上IPV6_V6ONLY默认关闭是错误的,这取决于操作系统而不仅仅是内核。例如,在Debian GNU/Linux上,它最近已经默认打开了。 - bortzmeyer
2
默认情况下,在Windows上启用(我刚在Win7上实现了这个)。 - tjd
2
如果IPV6_V6ONLY不可用,是否意味着操作系统不支持双栈? - Andrius Bentkus
1
@bortzmeyer 我使用的是 Linux StretchIPV6_V6ONLY 默认情况下是关闭的(我已经验证过了)。即使 man ipv6 也指定了:此标志的默认值由文件 /proc/sys/net/ipv6/bindv6only 的内容定义。该文件的默认值为0(false)。 我认为你的帖子可能是指旧版的 Linux Debian 发行版。 - patryk.beza
2
你能给我指一下同时支持IPv4和IPv6连接的示例源代码吗(还要解析IPv4映射格式)? - Brijesh Valera
显示剩余3条评论

9

套接字API受IETF RFC的管理,并且应该在所有平台上都相同,包括支持IPv6的Windows。

对于IPv4/IPv6应用程序,一切都关乎getaddrinfo()getnameinfo()getaddrinfo是一个天才-它查看客户端的DNS、端口名称和能力来解决“是否可以使用IPv4、IPv6或两者都到达特定目标”这个永恒的问题?如果您选择双栈路线,并希望它返回IPv4映射的IPv6地址,它也会做到。

它提供了一个直接的sockaddr *结构,可以插入bind()recvfrom()sendto()socket()的地址族……在许多情况下,这意味着没有混乱的sockaddr_in(6)结构要填写和处理。

对于UDP实现,我会谨慎设置双栈套接字,或者更普遍地绑定到所有接口(INADDR_ANY)。经典问题是,当地址没有锁定(参见bind())到特定接口并且系统具有多个接口请求时,响应可能从不同的地址传输,针对具有身份验证要求的应用程序协议可能会产生混淆。

对于不会出现这种问题的UDP实现,或者TCP,双栈套接字可以节省大量时间,用于启用IPv*系统。必须注意,在不绝对必要的情况下不要完全依赖双栈,因为部署了不支持双栈套接字的IPv6堆栈的合理平台(旧版Linux、BSD、Windows 2003)并不少见。


问题是... manpages 中有关这些函数的文档并不清楚,它“只是”用于 IPv4 和 IPv6,并且读起来像它将为任何具有解析为单词的主机名的协议族提供 struct sockaddr... 还有整个“hints”事情... 它到底有多少“提示”。在我看来,这不是“提示”,而是“命令”。:P 此外,当启用了 IPv6 但实际上没有路由到任何地方时,它返回大量的 ipv6 sockaddr 会有相当多的操作风险,因此需要通过 connect() 超时进行循环。 - HRH Sven Olaf of CyberBunker

4

我一直在Windows下测试这个,实际上它似乎确实存在安全问题。如果你绑定到回环地址,那么IPv6套接字将正确绑定到[::1],但映射的IPv4套接字将绑定到INADDR_ANY,因此你的(应该是)仅限于本地的应用程序实际上会暴露给全世界。


3
RFC并没有明确规定IPV6_V6ONLY套接字选项的存在,但如果它不存在,RFC非常清楚实现应该像该选项为假一样。
如果该选项存在,我认为它应该默认为假,但由于某些难以理解的原因,BSD和Windows实现将其默认为真。有一个奇怪的说法认为这是安全问题,因为不知情的IPv6程序员可能会绑定IN6ADDR_ANY,认为他们只绑定了IPv6,而意外接受IPv4连接,导致安全问题。我认为这既牵强又荒谬,而且让期望符合RFC的实现的人感到惊讶。
在Windows的情况下,不遵守通常不会令人惊讶。在BSD的情况下,这最多是不幸的。

6
IPv6 API的标准RFC 3493在其第5.3节中描述了IPV6_V6ONLY,如果您想阅读所有细节。 - bortzmeyer

1
正如Craig M. Brandenburg所观察到的那样,getaddrinfo承担了所有繁重的工作,使双重IPv4/IPv6成为可能。我在本地主机上有一个实验性的服务器和客户端。我在服务器中使用了这个:
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
...

客户端可以使用任何类型的地址连接到服务器:
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;

host_port = "4950"; // whatever

// All of these work.
host_ip = "127.0.0.1";        // Pure IPv4 address
host_ip = "::ffff:127.0.0.1"; // IPv4 address expressed as IPv6
host_ip = "::1";              // Pure IPv6 address
host_ip = "localhost";        // Domain name

int rv = getaddrinfo(host_ip, host_port, &hints, &result);
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接