UNIX网络编程卷一 学习笔记 第十二章 IPv4与IPv6的互操作性

2023-11-10

未来数年内,因特网也许会逐渐从IPv4过渡到IPv6,在过渡阶段,基于IPv4的现有应用能与基于IPv6的全新应用协同工作非常重要。例如,厂商不应只提供仅能与IPv6 telnet服务器程序协同工作的telnet客户程序,而既应该提供能与IPv4服务器协同工作的客户程序,又应该提供能与IPv6服务器协同工作的客户程序。更理想的情形是,一个IPv6的telnet客户程序既能与IPv4服务器程序协同工作,又能与IPv6服务器程序协同工作,相应地一个IPv6的telnet服务器程序既能与IPv4客户程序协同工作,又能与IPv6客户程序协同工作,下面介绍这是如何实现的。

本章假设主机运行着双栈,即一个IPv4协议栈和一个IPv6协议栈。在向IPv6转换的漫长过渡期内,主机和路由器会这样运行很多年。某个时间点后,许多系统可以关闭它们的IPv4协议栈,但只有时间才能告诉我们这种情况何时(以及是否)会发生。

双栈主机的一个基本特性是其上的IPv6服务器既能处理IPv4客户,又能处理IPv6客户,这是通过IPv4映射的IPv6地址实现的:
在这里插入图片描述
上图中,左侧有一个IPv4客户和一个IPv6客户,右侧的服务器程序使用IPv6编写,该服务器创建了一个绑定在IPv6通配地址和TCP端口9999上的IPv6 TCP监听套接字。

上图我们假设客户和服务器主机处于同一个以太网,它们也能通过路由器连接,只要通过的所有路由器都同时支持IPv4和IPv6,但这对我们的讨论没有影响。

我们假设这两个客户都发送SYN分节建立与服务器的连接。IPv4客户主机在一个IPv4数据报中载送SYN,IPv6客户主机在一个IPv6数据报中载送SYN。

来自IPv4客户的TCP分节在以太网线上表现为一个以太网首部后跟一个IPv4首部、一个TCP首部、TCP数据,以太网首部中包含的类型字段值为0x0800,即把本以太网帧标识为一个IPv4帧,TCP首部中包含的目的端口为9999,IPv4首部中包含的目的IP地址为206.62.226.42。

来自IPv6客户的TCP分节在以太网线上表现为一个以太网首部后跟一个IPv6首部、一个TCP首部、TCP数据。以太网首部中的类型字段值为0x86dd,即把本以太网帧标识为一个IPv6帧。这个TCP首部和IPv4数据报中的TCP首部格式完全一样,也包含值为9999的目的端口。IPv6首部中包含的目的IP地址为5f1b:df00:ce3e:e200:20:800:2b37:6426。

接收数据链路通过查看以太网类型字段把每个帧传递给相应的IP模块。IPv4模块结合其上的TCP模块检测到IPv4数据报的目的端口对应一个IPv6套接字,于是把该数据报IPv4首部中的源IPv4地址转换成一个等价的IPv4地址映射的IPv6地址。当accept系统调用把这个已经接受的IPv4客户连接返回给服务器进程时,这个映射后的地址将作为客户的IPv6地址返回到服务器的IPv6套接字。该连接上的其余数据报同样都是IPv4数据报。

当accept系统调用把接受的IPv6客户连接返回给服务器进程时,客户的IPv6地址就是IPv6首部中的源地址,该连接上其余数据报都是IPv6数据报。

总结以上一个IPv4客户和一个IPv6服务器通信的步骤:
1.IPv6服务器启动后创建一个IPv6监听套接字,假定服务器把通配地址捆绑到该套接字。

2.IPv4客户调用gethostbyname找到服务器主机的一个A记录。服务器主机既有一个A记录,又有一个AAAA记录,因为它同时支持IPv4和IPv6,但IPv4客户只需要A记录。

3.客户调用connect,导致客户主机发送一个IPv4 SYN到服务器。

4.服务器主机接收这个目的地为IPv6监听套接字的IPv4 SYN,设置一个标志指示本连接应使用IPv4映射的IPv6地址,然后响应一个IPv4 SYN/ACK。该连接建立后,由accept函数返回给服务器进程的地址就是这个IPv4映射的IPv6地址。

5.当服务器主机往这个IPv4映射的IPv6地址发送TCP分节时,其IP栈产生的目的地址为该IPv6地址对应的IPv4地址,因此客户和服务器之间都使用IPv4载送数据报。

6.除非服务器显式检查这个IPv6地址是不是一个IPv4映射的IPv6地址(可用IN6_IS_ADDR_V4MAPPED宏检查),否则它永远不知道自己是在与一个IPv4客户通信。这个细节由双协议栈处理,同样地,IPv4客户也不知道自己是在与一个IPv6服务器通信。

当IPv4 UDP客户与IPv6 UDP服务器进行通信时也类似。
在这里插入图片描述
对上图的解释:
1.如果收到一个目的地为某个IPv4套接字的IPv4数据报,则无需任何特殊处理。它们是上图标有IPv4的两个箭头,一个到TCP,一个到UDP。服务器和客户之间交换的是IPv4数据报。

2.如果收到一个目的地为某个IPv6套接字的IPv6数据报,则无需任何特殊处理。它们是上图标有IPv6的两个箭头,一个到TCP,一个到UDP。服务器和客户之间交换的是IPv6数据报。

3.如果收到一个目的地为某个IPv6套接字的IPv4数据报,则内核把该数据报的源IPv4地址映射的IPv6地址作为accept(TCP)或recvfrom(UDP)函数返回的对端IPv6地址。它们是上图中的两个虚线箭头。任何一个IPv4地址总能表示成一个IPv6地址。客户和服务器之间交换的是IPv4数据报。

4.一般,一个IPv6地址无法表示成一个IPv4地址,因此图中没有从IPv6协议框到IPv4套接字的箭头。

大多双栈主机使用以下规则处理监听套接字:
1.IPv4监听套接字只接受来自IPv4客户的外来连接。

2.如果服务器有绑定了通配地址的IPv6监听套接字,且该套接字没有设置IPV6_V6ONLY套接字选项,则该套接字既能接受来自IPv4客户的外来连接,又能接受来自IPv6客户的外来连接。对于来自IPv4客户的连接,其服务器端的对端IP地址是客户IPv4地址映射的IPv6地址。

3.如果服务器有一个IPv6监听套接字,且绑定在其上的是除IPv4映射的IPv6地址之外的某个非通配IPv6地址,或另一种情况,即绑定在其上的是IPv6通配地址,但设置了IPV6_V6ONLY套接字选项,则该套接字只能接受来自IPv6客户的外来连接。

对换一下上例中的客户和服务器使用的协议,考虑运行在双栈主机上的IPv6的TCP客户和运行在只支持IPv4上的TCP服务器:
1.一个IPv4服务器在只支持IPv4的一个主机上启动后创建一个IPv4监听套接字。

2.IPv6客户启动后调用getaddrinfo查找IPv6地址(通过请求AF_INET6地址,同时在hints结构中设置了AI_V4MAPPED标志)。由于只支持IPv4的那个服务器主机只有A记录,因此返回给客户的是一个IPv4映射的IPv6地址。

3.IPv6客户在IPv6套接字地址结构中设置这个IPv4映射的IPv6地址,然后用该地址结构调用connect。内核检测到此地址是IPv4映射的IPv6地址后自动发送一个IPv4 SYN到服务器。

4.服务器响应一个IPv4 SYN/ACK,连接于是通过使用IPv4数据报建立。

用下图汇总以上通信步骤:
在这里插入图片描述
对于上图的解释:
1.如果一个IPv4的TCP客户指定一个IPv4目的地址调用connect,或一个IPv4的UDP客户指定一个IPv4目的地址调用sendto,则无需任何处理。这指的是图中标有IPv4的两个箭头。

2.如果一个IPv6的TCP客户指定一个IPv6目的地址调用connect,或一个IPv6的UDP客户指定一个IPv6目的地址调用sendto,则无需任何处理。这指的是图中标有IPv6的两个箭头。

3.如果一个IPv6的TCP客户指定一个IPv4映射的IPv6目的地址调用connect,或一个IPv6的UDP客户指定一个IPv4映射的IPv6目的地址调用sendto,则内核检测到这个映射地址后,改为发送一个IPv4数据报而非IPv6数据报。这指的是图中两个虚线箭头。

4.不论是调用connect还是sendto,IPv4客户都不能指定一个IPv6地址,因为16字节的IPv6地址超出了IPv4的sockaddr_in结构中in_addr成员结构的4字节长度。因此上图中没有从IPv4套接字到IPv6协议框的箭头。

IPv4数据报发送到某个IPv6套接字的情形中,由IPv6套接字端的内核把收到的IPv4地址转换成IPv4映射的IPv6地址,并通过accept或recvfrom函数把映射后的IPv6地址透明地返回给应用进程;而通过某个IPv6套接字发送IPv4数据报的情形中,从IPv4地址到IPv4映射的IPv6地址之间的转换由解析器(如getaddrinfo函数)完成,映射后的IPv6地址随后由应用进程透明地传给connect或sendto函数。
在这里插入图片描述
上图中填有IPv4或IPv6的栏目表示相应的组合有效,并指出了实际使用的协议,标有(无)的栏目表示相应的组合无效。最后一行第三列标了星号,因为该栏目的互操作性取决于客户选择的地址,如果选择AAAA记录从而发送IPv6数据报,就不能工作;如果选择A记录,这个A记录实际作为IPv4映射的IPv6地址返回给客户,使得客户发送的是IPv4数据报,那就能工作。

尽管以上表格中有四分之一的组合不能互操作,但在可预见的将来,IPv6的多数实现都运行在双栈主机上,而不是IPv6单栈实现,因此,我们如果删去上表中第二行和第二列,则所有不能互操作的栏目都消失了,只剩下标了星号的栏目。

有些IPv6应用必须要清楚与其通信的对端是不是IPv4对端,头文件netinet/in.h中定义了以下12个宏用于测试一个IPv6地址的类型:
在这里插入图片描述
前7个宏测试IPv6地址的基本类型,后5个宏测试IPv6多播地址的范围。

IPv4兼容的IPv6地址用于不被看好的某个过渡机制,不太可能实际看到这类地址,没有测试它的必要(IN6_IS_ADDR_V4COMPAT)。IPv6 地址由128位组成,而IPv4地址只有32位。为了在IPv6中处理IPv4地址,IPv6兼容地址会在高位部分填充零,并在低位部分使用32位的IPv4地址,这样的地址结构使得IPv6协议栈能够处理与IPv4相关的操作。

IPv6客户可用IN6_IS_ADDR_V4MAPPED宏测试由解析器返回的IPv6地址,IPv6服务器可用该宏测试由accept或recvfrom函数返回的IPv6地址。

FTP的PORT指令是需要使用IN6_IS_ADDR_V4MAPPED宏的例子,如果启动一个FTP客户,登录到一个FTP服务器,然后发出FTP的dir命令,则FTP客户将通过控制连接向FTP服务器发送一个PORT指令,这条指令把客户的IP地址和端口号告知服务器,服务器随后据此建立一个数据连接。但IPv6的FTP客户必须清楚对端是IPv4服务器还是IPv6服务器,因为两者所需的PORT指令格式不同,如果对端是IPv4服务器,PORT的格式类似PORT a1,a2,a3,a4,P1,P2,前4个数字(每个都在0~255之间)构成一个4字节IPv4地址,后2个数字构成2字节的端口号;如果对端是IPv6服务器,则需要一个EPRT指令(它类似于PORT指令,允许客户端指定用于数据连接的地址和端口,它是PORT指令的优化版本,用以解决在某些网络配置下可能出现的问题,如NAT(网络地址转换)和防火墙会导致PORT指令出现问题,或IPv6地址格式与IPv4不同导致PORT指令出现问题),其中包含一个地址族、文本格式的地址、文本格式的端口号。

大多现有的网络应用是为IPv4编写的,这些应用填写一个或多个sockaddr_in结构,并且调用socket时第一个参数总是AF_INET。大多IPv4应用转换成IPv6应用并不费劲,有许多修改操作可用编辑脚本自动执行,较为依赖IPv4的程序转换起来比较麻烦,因为它们使用了诸如多播、IP首部选项字段、原始套接字等特性。

如果在源码上把一个应用的IPv4版本转换成IPv6版本并发布它,我们还需考虑使用者的系统是否支持IPv6。这个考虑的典型处理方法是在代码中到处使用#ifdef,以尽可能使用IPv6(IPv6客户仍能与IPv4服务器双向通信)。这种方法在代码中插入许多#ifdef,在代码理解和维护上造成困难。

更好的方法是把程序向IPv6的转换视为促成程序变得协议无关的一个机会。首先去除所有gethostbyname和gethostbyaddr函数,改用getaddrinfo和getnameinfo函数,这使得我们能把套接字地址结构作为不透明对象来处理,就像bind、connect、recvfrom函数所做的那样,用一个指针及大小来引用它们。第三章中的sock_XXX函数中含有#ifdef以处理IPv4和IPv6的不同,这样将所有协议相关的内容隐藏在函数中将简化我们的代码。

另一点需要考虑的是,如果我们在一个同时支持IPv4和IPv6的系统上编译源代码,然后发布其可执行代码或目标文件(而不是源码),但某个使用者却在不支持IPv6的系统上执行我们的程序,会发生什么?有一种可能:本地名字服务器支持AAAA记录,能够为应用尝试连接的对端主机返回AAAA记录和A记录,当应用调用socket创建IPv6套接字时,如果本地主机不支持IPv6,则socket函数将失败。我们可以忽略socket函数的错误,继续尝试由名字服务器返回的地址列表中的下一个地址,这些细节可由第十一章中的getaddrinfo函数的接口函数来处理。假如对端主机有一个A记录,名字服务器在返回所有的AAAA记录后还返回了这个A记录,就有可能成功创建IPv4套接字。这类功能应由某个库函数提供,而不应出现在每个应用程序的源码中。

IPV6_ADDRFORM套接字选项可以将IPv6套接字转换为IPv4-only或IPv6-only的套接字,但其语义从未被完整描述,且它仅在特定情况下才有用,因此后来把它删除了。现在某些系统里仍有IPV6_ADDRFORM套接字选项,可通过系统手册查看其语义。

双栈主机上的IPv6服务器既能服务IPv4客户,又能服务IPv6客户。IPv4客户发送给这种服务器的仍然是IPv4数据报,但服务器的协议栈会把客户主机的地址转换成IPv4映射的IPv6地址,因为IPv6服务器仅处理IPv6套接字地址结构。

双栈主机上的IPv6客户能和IPv4服务器通信,客户的解析器会把服务器主机所有的A记录作为IPv4映射的IPv6地址返回给客户,而客户指定这些地址之一调用connect将会使双栈发送一个IPv4 SYN分节。只有少量特殊客户和服务器需要知道对端使用的协议(如FTP),可在使用IPv6的程序中使用IN6_IS_ADDR_V4MAPPED宏判定对端是否在使用IPv4。

在一个运行IPv4和IPv6的双栈主机上启动一个IPv6的FTP客户,连接到一个IPv4的FTP服务器,确保客户处于主动模式(可能需要发出passive命令关闭被动模式),之后发出debug命令,然后是dir命令。然后对一个IPv6的FTP服务器执行同样操作,比较由dir命令引发的两个PORT指令。以下是相关的摘录片段,省掉了登录和列出的目录等内容,主机freebsd上的FTP客户不论服务器使用IPv4还是IPv6,总是先尝试EPRT命令,若不工作则退回到PORT命令:
在这里插入图片描述
在这里插入图片描述
当FTP客户处于主动模式时,当需要建立数据连接时,会给服务器发送一个端口号,让服务器主动开启数据连接。

如果FTP通信的双方的端口都是开放的,那么使用主动模式和被动模式都可以通信。而如果FTP的客户有防火墙,在FTP的主动模式下,FTP服务器会尝试连接FTP客户端的数据端口,但是由于客户端可能会有防火墙等网络安全设备的限制,导致连接失败。使用被动模式可以避免这个问题,因为在被动模式下,FTP服务器会监听一个端口(在passive命令的应答中返回),等待客户端连接然后发送给客户端数据。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UNIX网络编程卷一 学习笔记 第十二章 IPv4与IPv6的互操作性 的相关文章

  • meld - GLib-GIO-ERROR**:系统上未安装 GSettings 架构

    经过近40个小时的努力 我终于安装了meld 3 14 2 在Redhat 6 3服务器的NFS共享上 安装了每个依赖项 最后似乎成功了 但最后一个错误需要解决 meld 20703 GLib GIO ERROR No GSettings
  • shell脚本“x$VARIABLE”中x的用途[重复]

    这个问题在这里已经有答案了 我正在查看一些 shell 脚本 comarison shcu 中 x 的用途是什么 if x USER x RUN AS USER then su RUN AS USER c CATALINA HOME bin
  • 如何复制每个扩展名为 X 的文件,同时保留原始文件夹结构? (类Unix系统)

    我正在尝试将每个 HTML 文件从 src 文件夹复制到 dist 文件夹 但是 我想保留原始文件夹结构 如果 dist 文件夹不存在 我想创建一个新文件夹 如果文件夹不存在则创建 d dist mkdir dist 复制每个文件 cp R
  • 如何在gcc中打印UINT64_t?

    为什么这段代码不起作用 include
  • 类unix系统中的python和python3命令有什么区别?

    我通读了每个命令的描述 但每个命令的描述都是完全相同的 所以我不明白这两个命令在类 Unix 系统中的工作方式有何不同 谁能解释其中的区别吗 Python3命令的引入是因为python命令指向了python2 从那时起 Python3 已成
  • 在C语言中如何清屏? [复制]

    这个问题在这里已经有答案了 我想清除屏幕上的所有文字 我尝试过使用 include
  • sleep 0 有特殊含义吗?

    我看到很多用法sleep 0在我的一个客户项目中 代码看起来像这样 while true sleep 0 end 阅读一些像这样的答案this https stackoverflow com questions 3727420 signif
  • tar 和 zip 有什么区别? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 tar 和 zip 有什么区别 每个的用例是什么 tar其本身只是将文件捆绑在一起 结果称为tarball 尽管zip也应用压缩 通常你使用gzip随
  • 抑制 makefile 中命令调用的回显?

    我为一个作业编写了一个程序 该程序应该将其输出打印到标准输出 分配规范需要创建一个 Makefile 当调用它时make run gt outputFile应该运行该程序并将输出写入一个文件 该文件的 SHA1 指纹与规范中给出的指纹相同
  • 如何在 shell 脚本中操作 $PATH 元素?

    有没有一种惯用的方法从类似 PATH 的 shell 变量中删除元素 这就是我想要的 PATH home joe bin usr local bin usr bin bin path to app bin and remove or rep
  • 添加要在给定命令中运行的 .env 变量

    我有一个 env 文件 其中包含如下变量 HELLO world SOMETHING nothing 前几天我发现了这个很棒的脚本 它将这些变量放入当前会话中 所以当我运行这样的东西时 cat env grep v xargs node t
  • 如何查看正在运行的 tcsh 版本?

    如何查看我的 UNIX 终端中运行的 tcsh 的当前版本 看着那 这version多变的 echo version tcsh 6 14 00 Astron 2005 03 25 i386 intel linux options wide
  • 如何在 Bash 中给定超时后终止子进程?

    我有一个 bash 脚本 它启动一个子进程 该进程时不时地崩溃 实际上是挂起 而且没有明显的原因 闭源 所以我对此无能为力 因此 我希望能够在给定的时间内启动此进程 如果在给定的时间内没有成功返回 则将其终止 有没有simple and r
  • 按进程名称过滤并记录 CPU 使用情况

    Linux 下有选项吗顶部命令 https www man7 org linux man pages man1 top 1 html我可以在哪里按名称过滤进程并将每秒该进程的 CPU 使用情况写入日志文件 top pgrep 过滤输出top
  • waitpid() 的作用是什么?

    有什么用waitpid 它通常用于等待特定进程完成 或者如果您使用特殊标志则更改状态 基于其进程 ID 也称为pid 它还可用于等待一组子进程中的任何一个 无论是来自特定进程组的子进程还是当前进程的任何子进程 See here http l
  • 通过 sed 使用 unix 变量将数据附加到每行末尾[重复]

    这个问题在这里已经有答案了 我有一个文件 我想使用 SED 将值附加到每行末尾的 unix 变量中 我已经通过 AWK 实现了这一点 但我想在 SED 中实现 像这样的东西 我已经尝试过以下命令 但它不起作用 sed i s BATCH R
  • 监视目录的更改

    很像一个类似的问题 https stackoverflow com questions 112276 directory modification monitoring 我正在尝试监视 Linux 机器上的目录以添加新文件 并希望在这些新文
  • 如何在unix中移动或复制“find”命令列出的文件?

    我有使用下面的命令看到的某些文件的列表 但是如何将列出的这些文件复制到另一个文件夹 例如 test 中 find mtime 1 exec du hc 添加到 Eric Jablow 的答案中 这是一个可能的解决方案 它对我有用 linux
  • 用户的完整 UNIX 用户名

    想知道您是否知道是否有一种巧妙的方法可以从 shell 获取完整的用户名 示例 如果我的 UNIX 用户名是 froyo 那么我想获取我的全名 在本例中 如系统中注册的那样 froyo Abhishek Pratap Finger 命令可以
  • #*/ 在 UNIX Shell 脚本中使用

    谁能详细说明 在 UNIX Shell 脚本中的工作原理 我已经看到它在 Korn Shell 中的使用 它专门用于删除文件的扩展名 例如 func write app log o 删除状态文件 CIE STATUS FILE 这里假设文件

随机推荐