【发布时间】:2011-01-21 07:34:11
【问题描述】:
多年来,我使用 WinSock 为 Windows 开发了少量 C++ 服务器/客户端应用程序(路由器、Web/邮件/FTP 服务器等...等等...)。
我开始越来越多地考虑创建这些应用程序的 IPv6 版本(当然,同时也要保留原始 IPv4 版本)。
问题:
- 我可能会遇到什么陷阱?
- 移植/转换困难吗?
- 转换值得吗?
作为参考(或为了好玩),您可以在我的应用程序的核心中窥探IPv4 code 的峰值。
【问题讨论】:
多年来,我使用 WinSock 为 Windows 开发了少量 C++ 服务器/客户端应用程序(路由器、Web/邮件/FTP 服务器等...等等...)。
我开始越来越多地考虑创建这些应用程序的 IPv6 版本(当然,同时也要保留原始 IPv4 版本)。
问题:
作为参考(或为了好玩),您可以在我的应用程序的核心中窥探IPv4 code 的峰值。
【问题讨论】:
查看一些已实现 IPv6 的开源项目的更改日志。其中大部分是 Unix 代码,但 Winsock 与 BSD 套接字非常相似。
Exim、Courier、Squid、Apache、BIND DNS 是一些开始寻找的地方。
【讨论】:
大约一年前,我在以前仅支持 IPv4 的 networking library 中添加了 IPv6 支持,我并没有发现这样做非常困难或痛苦。
唯一的大区别是您如何存储 IP 地址:
在 IPv4 中,您将它们存储为 sockaddr_in(或者如果您像我一样顽皮,则存储为 uint32_t)。
对于 IPv6,您需要将它们存储为 sockaddr_in6(或一些等效的 128 位结构)。
一个好的转换前步骤是检查您的代码并找到当前存储 IPv4 地址的所有位置,并将它们抽象为一个通用 IP 地址类,以后可以在内部重新实现为 IPv4 地址 或 IPv6 地址。
然后重新测试以确保在 IPv4 模式下没有任何问题......一旦检查出来,您应该能够切换到 IPv6 只需再多几个变化(主要是把PF_INET改成PF_INET6,inet_aton()改成inet_pton(),等等……)。
默认情况下,我的库仍以 IPv4-only 的形式提供,但可以选择定义预处理器宏 (-DMUSCLE_USE_IPV6) 以在 IPv6 感知 模式下重新编译它.
这样它仍然可以在不支持 IPv6 的系统上编译。我在此过程中发现的一项非常有用的功能是 IPv4 映射的 IPv6 地址:
通过指定其中一个(本质上是一个带有 0xFFFF 前缀的 IPv4 地址),您将获得一个可以同时与 IPv4 和 IPv6 通信的套接字,从而获得一个可以同时与 IPv4 和 IPv6 客户端通信的服务器 em>,无需为所有内容编写单独的 IPv4 和 IPv6 代码路径。
至于是否值得努力,这实际上取决于您打算如何处理代码。如果没有别的,我会说这是一次很好的教育体验,而且它确实允许您的软件在 IPv6 环境中使用,随着时间的推移,这将变得更加普遍。
【讨论】:
getaddrinfo 和 getnameinfo 是您的朋友。在您寻求在现有应用程序中提供 IPv4 和 IPv6 支持的过程中,我尽可能建议他们成为您最好的朋友。
如果通过添加 IPv6 支持做得好,您最终还会将系统抽象到一个未知的未来 IP 协议可以在不修改代码的情况下运行的地步。
通常在连接时您会填写套接字结构、端口、地址族、IP 地址、将地址/端口转换为网络字节顺序等。
使用getaddrinfo,您发送一个IP 地址或主机名和端口或端口名称,它会返回一个链接列表,其中包含可以直接传递给socket() 和connect() 的结构和所有内容。
getaddrinfo 对于使用两种 IP 协议都至关重要,因为它知道主机是否具有 IPv6 或 IPv4 连接,并且它通过查看 DNS AAAA 与 A 记录并动态计算出知道对等方是否也这样做哪些协议可用于服务特定的连接请求。
我强烈建议不要使用inet_pton()、inet_addr() 或特定于 IP 版本的类似设备。在 Windows 平台上,特别是 inet_pton() 与早期版本的 MS Windows(XP,2003 等)不兼容,除非您自己推出。还建议不要使用 IPv4 和 IPv6 的单独版本……这作为技术解决方案是行不通的,因为在不久的将来,这两种协议都需要同时使用,而且人们可能不会提前知道要使用哪个。套接字接口是抽象的,通过尝试创建 IPv6 套接字或尝试为侦听器设置 IPv6 双堆栈套接字选项,很容易检测到双堆栈或 IPv6 支持。没有理由生成的应用程序不会在不支持或不了解 IPv6 的系统上运行。
对于传出连接,请在getaddrinfo 中使用PF_UNSPEC,以便在进行传出连接时为您选择地址系列。恕我直言,这比双栈方法更好,因为它允许不支持双栈的平台工作。
对于传入的连接,如果设计合理,您可以单独绑定 IPv4/IPv6 套接字,如果您不能做单独的侦听器,则可以使用双栈。当使用双栈getnameinfo 时,会为 IPv4 地址返回一个 IPv6 地址,恕我直言,这最终变得毫无用处。一个小的实用程序可以将字符串转换为普通的 IPv4 地址。
根据我的经验,如果做得好,您已经删除了对特定 IP 版本的依赖,并且最终得到的套接字管理代码比您开始时要少。
【讨论】:
glibc 的维护者 Ulrich Drepper 有一篇关于该主题的好文章,
http://people.redhat.com/drepper/userapi-ipv6.html
但不要忘记 Richard Steven 的书,Unix Network Programming, Volume 1: The Sockets Networking API 以获得良好实践。
【讨论】: