转换 C++从 IPv4 到 IPv6 的 TCP/IP 应用。难的?值得这么麻烦吗?
多年来,我使用 WinSock 为 Windows 开发了少量 C++ 服务器/客户端应用程序(路由器、Web/邮件/FTP 服务器等...等等...)。
我开始越来越多地考虑创建这些应用程序的 IPv6 版本(当然,同时也保留原始的 IPv4 版本)。
问题:
- 我可能会遇到哪些陷阱?
- 移植/转换困难吗?
- 转换值得吗?
作为参考(或为了好玩),您可以在以下位置查看 IPv4 代码 的峰值:我的应用程序的核心。
Over the years I've developed a small mass of C++ server/client applications for Windows using WinSock (Routers, Web/Mail/FTP Servers, etc... etc...).
I’m starting to think more and more of creating an IPv6 version of these applications (While maintaining the original IPv4 version as well, of course).
Questions:
- What pitfalls might I run into?
- Is the porting/conversion difficult?
- Is the conversion worth it?
For a reference (or for fun), you can sneek a peak of the IPv4 code at the core of my applications.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
getaddrinfo 和 getnameinfo 是您的朋友。在您寻求在现有应用程序中提供 IPv4 和 IPv6 支持的过程中,我尽可能建议他们成为您最好的朋友。
如果添加 IPv6 支持正确的话,您最终还会将系统抽象到无需修改代码即可运行未知的未来 IP 协议的程度。
通常,在连接时,您会填写套接字结构、端口、地址族、IP 地址、将地址/端口转换为网络字节顺序等。
使用
getaddrinfo
您可以发送 IP 地址或主机名以及端口或端口name,它返回一个链表,其中包含结构和所有内容,可以直接传递到socket()
和connect()
中。getaddrinfo
对于使用这两种 IP 协议至关重要,因为它知道主机是否具有 IPv6 或 IPv4 连接,并且通过查看 DNSAAAA
与 < code>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 版本的依赖,并且最终获得的套接字管理代码比开始时要少。
getaddrinfo and getnameinfo are your friends.. As much as possible I suggest they be your best friends in your quest to provide IPv4 and IPv6 support in an existing application.
If done right by adding IPv6 support you also end up abstracting the system to the point where an unknown future IP protocol can run without code modification.
Normally when connecting you would fill out a socket structure, port, address family, IP address, converting address/ports to network byte order, etc.
With
getaddrinfo
you send an IP address or hostname and port or port name, and it returns a linked list with the structures and everything ready to be passed directly intosocket()
andconnect()
.getaddrinfo
is critical for working with both IP protocols as it knows if the host has IPv6 or IPv4 connectivity and it knows if the peer does as well by looking at DNSAAAA
vsA
records and dynamically figures out which protocol(s) are available to service the specific connection request.I highly advise against use of
inet_pton()
,inet_addr()
or smiliar devices that are IP version specific. On the Windows platform specificallyinet_pton()
is not compatible with earlier versions of MS Windows (XP, 2003 et al.) unless you roll your own. Also advise against separate versions for IPv4 and IPv6... This is unworkable as a technical solution because in the near future both protocols will need to be used concurrently and people may not know ahead of time which to use. The socket interfaces are abstract and it's easy to detect dualstack or IPv6 support by attempting to create an IPv6 socket or attempt to set the IPv6 dualstack socket option for listeners. There is no reason the resulting application won't run on a system that does not support or know about IPv6.For outgoing connections use
PF_UNSPEC
ingetaddrinfo
so that the address family is chosen for you when making outgoing connections. This, IMHO, is better than the dualstack approach because it allows platforms that do not support dualstack to work.For incoming connections you can either bind IPv4/IPv6 sockets separately if it's reasonable given the design or use dualstack if you can't do separate listeners. When using dualstack
getnameinfo
returns an IPv6 address for IPv4 addresses which IMHO ends up being quite useless. A small utility routine can convert the string to a normal IPv4 address.From my experience when done right you've removed dependencies on specific IP versions and ended up with less socket management code than you started.
我花了大约一年的时间为之前仅支持 IPv4 的 网络库 添加了 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 的形式发布,但可以选择定义预处理器宏 (
-DMUSCLE_USE_IPV6
),以便在支持 IPv6 中重新编译它强>模式。这样它仍然可以在不支持 IPv6 的系统上编译。我一路上发现的一个非常有用的功能是IPv4 映射的 IPv6 地址:
通过指定其中之一(本质上是一个前缀为
0xFFFF
的 IPv4 地址),您将获得一个可以与 IPv4 和 IPv6 通信的套接字,从而获得一个可以与 IPv4 和 IPv6 客户端通信的服务器< em>同时,无需为所有内容编写单独的 IPv4 和 IPv6 代码路径。至于是否值得付出努力,这实际上取决于您打算对代码做什么。我想说,如果没有别的,这是一次很好的教育体验,而且它确实允许您的软件在 IPv6 环境中使用,随着时间的推移,这将变得更加普遍。
I added IPv6 support to my previously-IPv4-only networking library about a year ago, and I didn't find it terribly difficult or traumatizing to do.
The only big difference is how you store IP addresses :
In IPv4 you store them as
sockaddr_in
's (or if you're naughty, like me, as uint32_t's).For IPv6 you need to store them as
sockaddr_in6
's (or some equivalent 128-bit structure).A good pre-conversion step would be to go through your code and find all of the places where IPv4 addresses are currently stored, and abstract them out into a generic IP Address class that can later be reimplemented internally to be either an IPv4 address or an IPv6 address.
Then re-test to make sure nothing is broken in IPv4 mode... once that's checked out, you should be able to make the switch to IPv6 with just a few more changes (mainly changing
PF_INET
toPF_INET6
,inet_aton()
toinet_pton()
, etc...).My library still ships as IPv4-only by default, but with the option of defining a preprocessor macro (
-DMUSCLE_USE_IPV6
) to recompile it in IPv6-aware mode.That way it can still be compiled on systems that don't support IPv6. One very useful feature I found along the way is IPv4-mapped IPv6 addresses:
By specifying one of these (essentially an IPv4 address with
0xFFFF
prepended to it), you get a socket that can talk both IPv4 and IPv6, and thus a server that can talk to both IPv4 and IPv6 clients simultaneously, without having to write separate IPv4 and IPv6 code paths for everything.As for whether it's worth the effort, that really depends on what you intend to do with the code. I'd say it's a good educational experience if nothing else, and it does allow your software to be used in IPv6 environments, which will become more common over time.
glibc 的维护者 Ulrich Drepper 有一篇关于该主题的好文章,
http://people .redhat.com/drepper/userapi-ipv6.html
但不要忘记 Richard Steven 的书,Unix 网络编程,第 1 卷:套接字网络 API,提供良好实践。
Ulrich Drepper, the maintainer of glibc, has a good article on the topic,
http://people.redhat.com/drepper/userapi-ipv6.html
But don't forget Richard Steven's book, Unix Network Programming, Volume 1: The Sockets Networking API for good practice.
查看一些已实现 IPv6 的开源项目的更改日志。大部分是 Unix 代码,但 Winsock 与 BSD 套接字非常相似。
Exim、Courier、Squid、Apache、BIND DNS 是一些可以开始寻找的地方。
Look at the change logs of some of the open source projects that have implemented IPv6. Most of it is Unix code but Winsock is very similar to BSD sockets.
Exim, Courier, Squid, Apache, BIND DNS are some places to start looking.