编写一个简单r程序
在当今的网络世界中,IPv4是网络的基础,但是在最近的十年中,由于以下原因而出现了问题:
对于这些简单但严重的问题,网络地址转换(NAT)和无类域间路由(CIDR)(请参阅相关概念 )已被用作权宜之计。 IPv6(也称为IPng(新一代IP))已被视为长期解决方案。
还计划对IPv4进行以下增强:
所有这些更改背后的关键规则是IPv6应用程序应继续与IPv4应用程序一起使用。 底线是IPv6应该支持混合的IPv6和IPv4环境。
本文将帮助您快速理解IPv6的概念,并在其中编写一个简单的程序。 让我们从IPv6寻址开始。
在IPv6中,存在三种类型的地址-单播,多播和任播。 我们在IPv4中拥有单播地址,并且许多系统也支持多播。 Anycast是IPv6定义的一种新型地址。
en0
上的IPv4地址9.185.101.1)。 有三种将IPv6地址表示为文本字符串的常规形式:
1.主要形式:首选形式是x:x:x:x:x:x:x:x
,其中“ x”是地址的八个16位块的十六进制值。 两个例子:
fe80:0:0:0:207:30ee:edcb:d05d
1080:0:0:0:1:700:200B:417C
第一个地址中有八个十六进制字段:
fe80
0
0
0
207
30ee
edcb
d05d
在IPv6中,我们不在字段中写入前导零。 也就是说,上面的第二个字段只是写为“ 0”,而不是“ 0000”。 请注意,每个字段中有4个十六进制数字。 每个十六进制数字是4位(并且可以表示0-F的十六进制值)。 这意味着每个字段中有16位(4个十六进制数字x 4个数字/位数)。 IPv6地址的总大小为128位(8个十六进制字段x每个字段16位)。
2.上述地址的另一种表示形式:由于某些分配某些样式的IPv6地址的方法,地址通常包含零位长字符串。 为了使写入包含零位的地址更容易,可以使用特殊语法来压缩零。 ::
的使用表示16位零的多个组。 ::
只能在地址中出现一次,也可以用于压缩地址中的前导零。 例如:
FF01:0:0:0:0:0:0:101
是可以写为FF01::101
的多播地址。 0:0:0:0:0:0:0:1
是可以写为::1
的回送地址。 3.对于双重环境:处理IPv4和IPv6节点的混合环境时,有时更方便的另一种形式是x:x:x:x:x:x:dddd
,其中“ x”是十六进制值地址的六个高阶16位元中的一个,“ d”是地址的四个低阶8位元(标准IPv4表示)的十进制值-即前96个这些位表示为6 x 16位十六进制字段,最后32位为4 x 8位十进制数字。 例如:
::9.184.201.1
::ffff:9.184.209.2
IPv6地址前缀
IPv6地址前缀表示地址的网络部分,并用符号ipv6-address/prefix-length
。
举个例子:
fe80::206:29ff:fedc:e06e/64
在这种情况下, fe80::206:29ff:fedc:e06e
是地址,而64是前缀长度。 这两个一起为我们提供了地址前缀。 在示例中,指定64表示我们采用上述128位地址的前64位来标识该地址的网络部分。
这就提出了几个问题:
这里是答案:
地址空间:关于地址空间问题,IPv6工作中的关键人物之一Robert M Hinden解释说:
IPV6支持的地址是IPv4地址的位数的四倍(128对32)。 这是IPv4地址空间大小(2 ^ 32)的40亿乘以40亿乘以40亿(2 ^ 96)倍。 结果是:
340,282,366,920,938,463,463,374,607,431,768,211,456
这是一个非常大的地址空间。 从理论上讲,这大约是地球每平方米表面的665,570,793,348,866,943,898,599个地址(假设地球表面为511,263,971,197,990平方米)。
阶级敌人:现在让我们来解决有关IPv4和IPv6中的地址前缀的问题。 将IPv4地址空间划分为A,B,C和D类网络引起了一些问题。 在IPv4中,网络部分由地址类别固定。 让我们用一个例子来说明我们的观点。 A类地址可以在其128个网络中的每个网络上支持1600万个主机(因为在A类地址中,最高位设置为0;后7位用于网络部分;其余24位用于用于本地地址)。 现在,如果给一个组织分配了A类地址,并且没有1600万个主机,则剩余的地址空间将被浪费。 还要注意,不能给每个人A类地址,因为只有127类。必须引入CIDR才能解决此问题并延长IP寿命。 这意味着地址的网络部分不应固定。 显然需要特定于组织的网络规模。 这意味着地址的网络部分不应固定。 通过允许用户在地址前缀中指定网络位,可以在IPv6中实现此可变前缀长度。 例如,在地址fe80::206:29ff:fedc:e06e/64 -
,数字64表示网络部分,可以更改。 在这里,我们可以选择网络部分。 这是灵活的,与IPv4始终固定的不同。
路由表: Internet中的路由随时间增长。 骨干路由器在1984年接近其极限。如果不引入CIDR来解决全球骨干路由器的空间问题,它们将被终止。
CIDR技术:那么IPv6如何解决这个问题? 解决此问题的技术是允许满足特定组织需求的地址前缀。 此技术基本上是在CIDR中引入的。 在IPv6中,前缀或网络部分也由用户指定的网络前缀指定。 这有助于聚合大量IP地址并为组织指定一条路由。 如果组织具有多个网络,则在IPv4的情况下,将在全局路由表中指定许多网络前缀。 对于IPv6,我们可以简单地给出一个更高级别的路由来代表整个组织,因为我们可以通过改变它来缩小和扩展网络前缀。 这有助于使全局表保持较小。 这种设置在IPv4中不存在。 (有关CIDR的更多信息,请参阅相关概念 )。
什么是自动配置? 首先要做的是用IPv6地址设置一台机器。 有一个在IPv6的一个有趣的功能,称为无状态自动配置多数民众赞成由RFC 2462定义(参见相关主题 )。 该RFC声明您的主机应该能够为您提供一个自动的,全球唯一的IPv6地址。
例如,在AIX中,只需启动机器并从#
提示符下键入autoconf6 -v
,您将看到机器自动检测子网并为您分配有效的IPng地址。
我运行了ifconfig
来查看IPv6地址。 这是我的AIX机器上ifconfig -a
的部分输出:
inet 9.184.209.3 netmask 0xffffff00 broadcast 9.184.209.255
inet6 fe80::207:30ee:edcb:d05d/64
当我运行autoconf -v6
时,我得到了inet6
地址( inet6
在en0
上定义)。 现在,该计算机在同一物理以太网接口上同时具有IPv6和IPv4。
怎么做? 用非常简单的术语来说,链路层地址用作获取IPv6地址以及主机和路由器进行通信的基础,以便主机可以了解有关子网的信息。 (有关更详细的讨论,请参考RFC。)
其他操作系统怎么样? 其他UNIX实现具有类似AIX的类似IPv6自动配置命令。 还有许多IPv6的自由软件实现(请参阅参考资料 )。
我可以手动配置吗? 是。 您还可以使用ifconfig
配置IPv6地址。 计划网络以分配网络前缀很重要。
过渡问题的例子
考虑这种情况。 我们有一个现有的IPv4环境,其中只有IPv4主机和路由器。 现在,我们在网络中添加了一些IPv6路由器和主机。 这些主机中的一些具有处理IPv6和IPv4地址的能力,其中一些是纯IPv6或纯IPv4。 如果我们必须编写在此环境中运行的应用程序,则该应用程序的客户端和服务器应该能够处理所有可能的客户端-服务器对。 也就是说,客户端或服务器可以完全是IPv4,完全是IPv6,或者既支持IPv6又支持IPv4。 (有关详细说明,请阅读RFC 2893:“主机和路由器的转换机制” -请参阅参考资料 。)
什么是隧道技术? 再次,让我们举个例子。 我们需要在IPv4网络上传送IPv6数据包。 我们该如何进行? 简单-我们只是将IPv6数据包封装在IPv4数据包中,然后通过IPv4网络发送它。 这称为隧道 。
配置的隧道:我们需要配置位于IPv4网络入口点的主机,以便它可以将IPv6数据包转换为IPv4数据包。 另外,需要配置作为IPv4网络出口点的节点,以便它可以将数据包转换回IPv6数据包。 这称为配置隧道 。
自动隧道:如果主机具有动态进行此转换的能力,则称为自动隧道 。
协议中对自动隧道的支持:为使用此技术的节点分配了特殊的IPv6单播地址。 这些地址以低阶32位传送IPv4地址。 这种类型的地址称为IPv4兼容IPv6地址 ,其格式如下:
| 80 bits | 16 | 32 bits |
+--------------------------------------+--------------------------+
|0000..............................0000|0000| IPV4 ADDRESS |
+--------------------------------------+----+---------------------+
还定义了保存嵌入式IPv4地址的第二种IPv6地址。 此地址用于表示仅IPv4节点( 不支持IPv6的节点)的地址作为IPv6地址。 这种类型的地址称为“ IPv4映射的IPv6地址”,其格式为:
| 80 bits | 16 | 32 bits |
+--------------------------------------+--------------------------+
|0000..............................0000|FFFF| IPV4 ADDRESS |
+--------------------------------------+----+---------------------+
映射地址的用法
如果要编写支持IPv6的客户端,则会遇到此问题:是发送IPv6数据包还是发送IPv4数据包? 您不能保证有关基础网络。 您与之建立连接的下一台计算机可以是IPv6计算机,IPv4计算机或双主机。
假设负责路由连接的应用程序能够知道下一台机器是IPv6机器还是IPv4机器。 在这种情况下,如果我们可以在内部包含IPv4地址的IPv6地址,那将是非常有用的。 最好有一种机制(映射的v4地址中的ffff.
)来告诉我们该地址是否指向纯IPv4节点。 这将有助于我们对要发送哪种类型的数据包做出适当的决定。 我们在最后一节中的讨论应该使这一点更加清楚。
将IPv4应用程序移植到IPv6时,需要考虑以下事项:
sockaddr_in6
结构和in6_addr
结构。 检查您是否正在使用相关的IPv6结构。 INADDR_ANY
和INADDR_LOOPBACK
修改为in6addr_any
或in6addr_loopback
进行分配。 IN6ADDR_ANY_INIT
或IN6ADDR_LOOPBACK_INIT
宏可能会有所帮助。 AF_INET6
而不是AF_INET
。 sockaddr_in6
为sockaddr*
结构。 以下宏和函数用于编写支持IPv6的应用程序:
IN6_IS_ADDR_V4MAPPED
可用于确定IPv6地址是否为IPv4映射的地址。 gethostbyname
通过其名称和地址族检索网络主机条目。 getaddrinfo
返回与指定服务位置有关的地址信息。 getnameinfo
返回与提供的IP地址和端口号关联的文本字符串。 inet_pton
将文本形式的指定地址转换为其等效的二进制地址。 inet_ntop
将指定的二进制地址转换为适合表示的文本等效项。 getaddrinfo
和getnameinfo
均可用于检索与IPv4和IPv6地址有关的信息。 inet_pton
和inet_ntop
都可以转换IPv4和IPv6地址。 这意味着,在“支持IPv6的”应用程序,你不需要为使用inet_addr
或inet_ntoa
。 bind
, connect
, sendmsg
, sendto
, accept
, recvfrom
, recvmsg
, getpeername
和getsockname
,尽管这些函数的代码已被修改。 现在让我们看一下编写支持IPv6的客户端背后的逻辑。 我相信我们具备基本知识。 我们知道IPv6地址。 如果我们看到它们以不同的表示形式,我们将能够识别它们。 我们将能够使用autoconf在我们的计算机上自动配置IPv6地址。 我们还了解映射的地址转换机制,并对使用的功能有所了解。 考虑以下IPv4客户端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <netdb.h>
...
main(argc, argv) /* client side */
int argc;
char *argv[];
{
struct sockaddr_in server;
struct servent *sp;
struct hostent *hp;
int s;
...
sp = getservbyname("login", "tcp");
if (sp == NULL) {
fprintf(stderr, "rlogin: tcp/login: unknown service\n");
exit(1);
}
hp = gethostbyname(argv[1]);
if (hp == NULL) {
fprintf(stderr, "rlogin: %s: unknown host\n", argv[1]);
exit(2);
}
memset((char *)&server, 0, sizeof(server));
memcpy((char *)&server.sin_addr, hp->h_addr, hp->h_length);
server.sin_len = sizeof(server);
server.sin_family = hp->h_addrtype;
server.sin_port = sp->s_port;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("rlogin: socket");
exit(3);
}
...
/* Connect does the bind for us */
if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("rlogin: connect");
exit(5);
}
我们将研究将其转换为支持IPv6的客户端的逻辑。
sockaddr_in6结构:我们正在使用struct sockaddr_in
结构。 我们不能使用相同的结构,因为成员sin_addr
只能容纳32位。 在将客户端移植到IPv6客户端时,我们需要使用sockaddr_in6
,它可以容纳128位地址。
struct sockaddr_in6 {
u_char sin6_len;
u_char sin6_family;
u_int16_t sin6_port;
u_int32_t sin6_flowinfo;
struct in6_addr sin6_addr;
};
家庭: sin6_family
在我们的程序中将具有AF_INET6
而不是AF_INET
。
sin6_flowinfo字段:应用程序可以通过设置目标地址sockaddr_in6
结构的sin6_flowinfo
字段来指定流标签和优先级。 我们现在可以将其设置为0。
客户将处理哪种类型的地址? 我们有三种情况。 用户可以通过:
IPv6地址:如果它是用冒号分隔的IPv6地址,那么我们可以将其复制到结构中。
IPv4地址:如果是IPv4地址,我们需要将其复制到最后32位,并用0xffff
标记这32位之前的16位。
主机名:如果是主机名,那么我们使用gethostbyname
来获取地址。 默认情况下, gethostbyname
选择一个IPv4地址。
如果在AIX中setting _res.options
( resolv.h
)后调用gethostbyname
,则可以强制它进行IPv6查找:
_res.options |= ~RES_USE_INET6
请注意,如果主机名不存在IPv6地址(在UNIX或DNS中的/ etc / hosts中),则通过gethostbyname
进行IPv6查找将返回一个IPv4地址,但是我们仍然需要进行映射(填充位) 81-96与0xffff
)。 此外,某些实现还对IPv6查找进行了另一个gethostbyname2
调用。
为什么要做映射? 我们进行此映射是为了在connect调用中使用sockadd_in6
结构,无论我们尝试发送到IPv6地址还是IPv4地址。 如果没有,我们将需要进行两个连接调用-一个使用IPv6地址,而一个使用IPv4地址。 另一种技术是使用sockaddr_in
和sockaddr_in6
结构的并集。 程序员也可以设计自己的技术,这不是强制性的。
// use the isinet_addr call to find out whether its a valid
// dotted ipv4 address
if (isinet_addr(hostname )) {
......
//now you might wonder what s6_addr16[5] is - this is basically a union member normally
//defined in in.h which will point to bits 81-96
ip6.sin6_addr.s6_addr16[5] = 0xffff;
//now we are copying the ipv4 address in the last 32 bits
bcopy(address, &ip6.sin6_addr.s6_addr16[6], sizeof(struct in_addr));
ip6.sin6_len = sizeof(struct in6_addr);
ip6.sin6_family = AF_INET6;
......
}
//check if its is an IPv6 : separated address - inet_pton is used for this
else if (inet_pton(AF_INET6, hostname, &ip6.sin6_addr) > 0) {
//note inet_pton will take care of setting the address
.....
ip6.sin6_family = AF_INET6;
ip6.sin6_len = sizeof(struct sockaddr_in6);
.....
}
else {
//now its not a v6 address or a v4 address so it should be host name
//do a v6 lookup , note that a v6 lookup will look for a v6 address if not
//present it can pick up a v4 address
//res init is defined in resolv.h
res_init();
_res.options |= RES_USE_INET6;
hptr = gethostbyname(name);
.....
//check hptr->h_addrtype if its AF_INET6 you can copy the address directly
//if not you need to map it.
.....
.....
if (connect(sd, &ip6, sizeof (ip6 < 0)
{
//connect failure
....
}
else
{
//continue with the program.
}
总结一下逻辑,我们检查是否有点IPv4地址要处理。 如果是这样,我们继续对其进行映射并填写一个IPv6结构,以供稍后的connect调用使用。 如果它是IPv6地址,我们将其直接复制到IPv6结构。 如果是主机名,我们尝试进行IPv6查找。 我们可以获取一个IPv4或IPv6地址。 我们从家庭领域知道这一点。 因此,我们将其映射或复制,然后进行单个connect调用,而不管它是IPv4还是IPv6地址,然后继续执行我们的程序。
我们仅查看了编写上述程序所需的概念。 还有许多有趣的概念将很快成为日常生活的一部分。 关于诸如IPv6的DNS和IPv6(DHCP)的有状态自动配置之类的争论和建设性辩论。 这些主题以及其他主题(例如其他层的实现,如何进行路由以及如何实现自动配置)将引起有趣的讨论。 我希望能在更加激动人心的IPv6世界中与您见面!
翻译自: https://www.ibm.com/developerworks/web/library/wa-ipv6.html
编写一个简单r程序