CSDN话题挑战赛第2期
参赛话题:学习笔记
IPv6格式
大小
IPv6地址大小为128位。地址范围从0000:0000:0000:0000:0000:000:000000至ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff。
IPv6将现有的IP地址长度扩大4倍,由当前IPv4的32位扩充到128位,以支持大规模数量的网络节点。这样IPv6的地址总数就大约有3.4*10E38个。平均到地球表面上来说,每平方米将获得6.5*10E23个地址。
格式
首选的IPv6地址表示为x:x:x:x:x:x:x:x,其中每个x是代表一个4位的十六进制数字。
IPv4有掩码,能将地址分成网络地址和主机地址,那么IPv6有个概念叫前缀prefix,如果是带前缀的话,
一个IPv6地址可以分为如下两部分,其中64为前缀长度,地址2001:A304:6101:1::E0:F726:4E58/64的构成如所示。
- 网络前缀(Nework prefix):n比特,相当于IPv4地址中的网络ID。n就是前缀长度
- 接口标识(Interface ID):128-n比特,相当于IPv4地址中的主机ID。
IPv6通常默认n为64,那么后64位就是接口标识,比如EUI-64算法就是通过64位来计算的。因此,在划分子网的时候,建议最好不要让接口标识小于64,否则可能会不支持或者出错。
缩写:
缩写有两种方式:省略前导零和双冒号
- 省略前导零
通过省略前导零指定IPv6地址。例如,IPv6地址 1050:0000:0000:0000:0005:0600:300c:326b可写为1050:0:0:0:5:600:300c:326bc。 - 双冒号
通过使用双冒号(::)代替一系列零来指定IPv6地址。例如,IPv6地址 ff06:0:0:0:0:0:0:c3可写为ff06::c3。一个IP地址中只可使用一次双冒号。
这里有一个化简原则:
- 全0块“0000”,可以化简为“0”
- 多个全0块,可以化简为“::”
- 一个IPv6地址中只能出现一个“::”,出现多个全0块时,“::”要化简最长的一段,没有最长的要就近(左)
- “::”可以出现在地址开头或结尾
所以各式各样的IPv6地址,需要好好的分辨一下,到底哪里缩写了
IPv6分类
IPv6地址可分为三大类:
- 单播地址
- 组播地址
- 任意播地址
单播地址
单播地址用于一对一的连接,IPv6的单播地址有以下六种类型,
- 可聚合全球单播地址(Aggregate Global Unicast Address)
可聚合全球单播地址,由IANA分配可在全球路由的公网IP地址,
目前地址范围为:2XXX::XXXX/3 - 3FFF::FFFF/3,占12.5%的IPv6地址空间,这个前缀中包含了8192个16的前缀,
地址 | 说明 |
---|---|
2001::/16 | 目前用于IPv6因特网运营的前缀 |
2002::/16 | 6to4过度地址 |
3ff3::/16 | 用于6bone测试目的的前缀 |
-
链路本地地址( Link Local Address)
FE80::/10(前10位以FE80开头)
当一个节点启用IPv6时,此节点会自动生成一个link-local address,其前缀64为标准指定,其后64位按照EUI-64格式来构造,在本地链路上,路由表中看到下一跳都是对端的Link Local地址,不是公网IP地址,这个地址只能在本地链路中使用,不能在子网间路由。
后面还有这个地址的介绍,有点意思。 -
IPv6的私网地址(Site Local Address)
FEC0::/10
IPv6的私网地址,就像IPv4中的私网保留地址一样,只能在本站点内使用,不能在公网上使用 -
未指定地址(Unspecified Address)
::/128
未指定地址,写默认路由时代表所有路由 -
本地址回环地址(Loopback Address)
::1/128
本地址回环地址,同IPv4中的127.0.0.1一样,表示节点自己 -
IPv4兼容IPv6的地址(IPv4 Compatible Address)
::192.168.1.2
IPv4兼容IPv6的地址,用于在IPv4网络上建立自动隧道,以传输IPV6数据
组播地址
在IPv6中没有广播,使用组播来代替,前缀FF00::/8,占用0.38%的地址空间,组播地址的格式如下:
标志为0000表示永久保留的组播地址,分配给各种地址使用
标志为0001表示用户可使用的临时组播地址
范围段定义了组播地址的范围,其定义如下:
二进制 | 十六进制 | 说明 |
---|---|---|
0001 | 1 | 本地接口范围 |
0010 | 2 | 本地链路范围 |
0011 | 3 | 本地子网范围 |
0100 | 4 | 本地管理范围 |
0101 | 5 | 本地站点范围 |
1000 | 8 | 组织机构范围 |
1110 | E | 全球范围 |
下面是一些组播制定的地址
FF02::1---------- all nodes 在本地链路范围内的所有节点
FF02::2---------- all routers 在本地范围内的所有路由器
FF05::2 ---------- 在一个站点范围内的所有路由器
任播地址Anycast address
任意播没有独立的地址空间,取决于路由器的配置,把一个单播地址设定成任意播。
一个IPv6任播地址与组播地址一样也可以识别多个接口,对应一组接口的地址。大多数情况下,这些接口属于不同的节点。但是,与组播地址不同的是,发送到任播地址的数据包被送到由该地址标识的其中一个接口。
通过合适的路由拓扑,目的地址为任播地址的数据包将被发送到单个接口(该地址识别的最近接口,最近接口定义的根据是因为路由距离最近),而组播地址用于一对多通信,发送到多个接口。
应用在one-to-nearest(一到近)模式。
任意播是多个设备共享一个地址。分配IPv6单播(unicast)地址给拥有相同功用的一些设备发送方发送一个以任意播为目标地址的包,当路由器接受到这个包以后,就转发给具有这个地址的离它最近的设备单播地址。用来分配任意播地址对于那些没有配备任意播的的地址就是单播地址,但是当一个单播地址分配给不止一个接口的时候单播地址就成了任意播地址。
例如:Mobile方面的特性,移动设备漫游到其他区域,不必接入原始的接入点,只需要找到最近的即可。
有的地方我也不懂,因为都是摘抄的资料
IPv6地址分配
IPv6把自动将IP地址分配给用户的功能作为标准功能。只要机器一连接上网络便可自动设定地址。它有两个优点。一是最终用户用不着花精力进行地址设定,二是可以大大减轻网络管理者的负担。IPv6有两种自动设定功能。一种是和IPv4自动设定功能一样的名为“全状态自动设定”功能。另一种是“无状态自动设定”功能。
在IPv4中,动态主机配置协议(DynamicHostConfigurationProtocol,DHCP)实现了主机IP地址及其相关配置的自动设置。IPv6继承了IPv4的这种自动配置服务,并将其称为全状态自动配置(StatefulAutoconfiguration)。
在无状态自动配置(StatelessAutoconfiguration)过程中,主机首先通过将它的网卡MAC地址附加在链接本地地址前缀1111111010之后,产生一个链路本地单点传送地址。接着主机向该地址发出一个被称为邻居发现(neighbordiscovery)的请求,以验证地址的唯一性。如果请求没有得到响应,则表明主机自我设置的链路本地单点传送地址是唯一的。否则,主机将使用一个随机产生的接口ID组成一个新的链路本地单点传送地址。然后,以该地址为源地址,主机向本地链路中所有路由器多点传送一个被称为路由器请求(routersolicitation)的配置信息。路由器以一个包含一个可聚集全球单点传送地址前缀和其它相关配置信息的路由器公告响应该请求。主机用它从路由器得到的全球地址前缀加上自己的接口ID,自动配置全球地址,然后就可以与Internet中的其它主机通信了。使用无状态自动配置,无需手动干预就能够改变网络中所有主机的IP地址。
举个例子说明这个无状态自动配置:
我们可以通过ifconfig来查看到网卡的IPv6地址
上面的inet6后面就是这个网卡的IPv6地址,注意看这个IP地址,其实就是前面通过“无状态自动配置”获得的IPv6的地址,
这里讲一下EUI-64格式
在IPv6中,无状态自动配置机制使用EUI-64格式来自动配置IPv6地址,所谓无状态自动配置是指在没有DHCP服务器的情况下,允许节点自行配置IPv6地址的机制,EUI-64的构造规则(根据接口的MAC地址再加上固定的前缀来生成一个IPv6的地址)
将48位的MAC地址扩展成64位,再挂在一个64位的前缀后面,组成一个IPv6地址。
以接口docker0为例,MAC地址02:42:4b:b5:67:48 ,转化过程如下:
-
将48位的MAC地址从中间分开加入一个固定的FFFE
02:42:4b:b5:67:48 ->0242:4BFF:FEb5:6748 -
将第7位反转,如果原来是0就变成1,如果原来是1就变成0,
0x0242的2进制0000001001000010,第七位为1,变为0,变为了0x0042
0242:4BFF:FEb5:6748->0042:4BFF:FEb5:6748 -
再加上前缀FE80::
FE80::0042:4BFF:FEb5:6748
这就是一个完整的IPv6地址
反转的原因是:
在MAC地址中,bit7为1表示本地管理,为0表示全球管理,在EUI-64格式中,bit7表示全球唯一,为0表示本地唯一。
不过这个IP地址,在socket通讯的时候,没办法使用,但是在ping6的时候,可以通,也是很奇怪
IPv6在socket上应用
那么我们如何在socket上运用IPv6地址,其实和IPv4过程一样,只是某些参数的配置,需要配置IPv6的地址。
直接上代码
服务端
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, new_fd;
socklen_t len;
/* struct sockaddr_in my_addr, their_addr; */ // IPv4
struct sockaddr_in6 my_addr, their_addr; // IPv6
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
if (argv[1])
{
myport = atoi(argv[1]);
}
else
{
myport = 7838;
}
printf("端口:%d\n",myport);
if (argv[2])
{
lisnum = atoi(argv[2]);
}
else
{
lisnum = 2;
}
printf("请求队列:%d\n",lisnum);
/* if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) */ // IPv4
if ((sockfd = socket(PF_INET6, SOCK_STREAM, 0)) == -1) // IPv6
{
perror("创建socket");
exit(1);
}
else
{
printf("创建socket成功\n");
}
bzero(&my_addr, sizeof(my_addr));
/* my_addr.sin_family = PF_INET; */ // IPv4
my_addr.sin6_family = PF_INET6; // IPv6
/* my_addr.sin_port = htons(myport); */ // IPv4
my_addr.sin6_port = htons(myport); // IPv6
if (argv[3])
{
/* my_addr.sin_addr.s_addr = inet_addr(argv[3]); */ // IPv4
inet_pton(AF_INET6, argv[3], &my_addr.sin6_addr); // IPv6
}
else
{
/* my_addr.sin_addr.s_addr = INADDR_ANY; */ // IPv4
my_addr.sin6_addr = in6addr_any; // IPv6
}
/* if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) */ // IPv4
if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_in6)) == -1) // IPv6
{
perror("bind");
exit(1);
}
else
{
printf("bind成功\n");
}
if (listen(sockfd, lisnum) == -1)
{
perror("listen");
exit(1);
}
else
{
printf("开始监听\n");
}
while (1)
{
len = sizeof(struct sockaddr);
if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len)) == -1)
{
perror("accept");
exit(errno);
}
else
{
printf("收到连接请求,来自 %s, 端口 %d, socket %d\n",
/* inet_ntoa(their_addr.sin_addr), */ // IPv4
inet_ntop(AF_INET6, &their_addr.sin6_addr, buf, sizeof(buf)), // IPv6
/* ntohs(their_addr.sin_port), new_fd); */ // IPv4
their_addr.sin6_port, new_fd); // IPv6
}
/* 开始处理每个新连接上的数据收发 */
bzero(buf, MAXBUF + 1);
strcpy(buf, "客户端你好");
/* 发消息给客户端 */
len = send(new_fd, buf, strlen(buf), 0);
if (len < 0)
{
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno, strerror(errno));
}
else
{
printf("消息发送成功\n");
}
bzero(buf, MAXBUF + 1);
/* 接收客户端的消息 */
len = recv(new_fd, buf, MAXBUF, 0);
if (len > 0)
{
printf("接收消息成功[%d]:'%s'\n", len, buf);
}
else
{
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
}
/* 处理每个新连接上的数据收发结束 */
}
close(sockfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, len;
/* struct sockaddr_in dest; */ // IPv4
struct sockaddr_in6 dest; // IPv6
char buffer[MAXBUF + 1];
if (argc != 3)
{
printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息", argv[0], argv[0]);
exit(0);
}
/* 创建一个 socket 用于 tcp 通信 */
/* if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { */ // IPv4
if ((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) // IPv6
{
perror("Socket");
exit(errno);
}
else
{
printf("创建socket成功\n");
}
/* 初始化服务器端(对方)的地址和端口信息 */
bzero(&dest, sizeof(dest));
/* dest.sin_family = AF_INET; */ // IPv4
dest.sin6_family = AF_INET6; // IPv6
/* dest.sin_port = htons(atoi(argv[2])); */ // IPv4
dest.sin6_port = htons(atoi(argv[2])); // IPv6
/* if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) { */ // IPv4
if ( inet_pton(AF_INET6, argv[1], &dest.sin6_addr) < 0 )// IPv6
{
perror(argv[1]);
exit(errno);
}
else
{
printf("地址转化成功\n");
}
/* 连接服务器 */
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
{
perror("Connect ");
exit(errno);
}
else
{
printf("服务器连接成功\n");
}
/* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
bzero(buffer, MAXBUF + 1);
/* 接收服务器来的消息 */
len = recv(sockfd, buffer, MAXBUF, 0);
if (len > 0)
{
printf("接收消息成功[%d]:'%s'\n", len, buffer);
}
else
{
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
}
bzero(buffer, MAXBUF + 1);
strcpy(buffer, "你好服务端");
/* 发消息给服务器 */
len = send(sockfd, buffer, strlen(buffer), 0);
if (len < 0)
{
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buffer, errno, strerror(errno));
}
else
{
printf("消息发送成功\n");
}
/* 关闭连接 */
close(sockfd);
return 0;
}
编译并运行
[root@localhost test]# gcc -o ipv6_client ipv6_client.c
[root@localhost test]# gcc -o ipv6_server ipv6_server.c
[root@localhost test]# ./ipv6_server 8888 2 ::1
端口:8888
请求队列:2
创建socket成功
bind成功
开始监听
收到连接请求,来自 ::70f3:687c:fe7f:0, 端口 2264, socket 4
消息发送成功
接收消息成功[15]:'你好服务端'
客户端
[root@localhost test]# ./ipv6_client ::1 8888
创建socket成功
地址转化成功
服务器连接成功
接收消息成功[15]:'客户端你好'
消息发送成功
敲黑板了!!!两台服务器通讯
前面的demo是本地通讯,监听的ip地址,为::1本地IP地址。
那么能不能用接口上面无状态配置的IP呢,也就是这个链路本地地址
例如我的接口上的fe80::b605:5dff:fea2:8f04
eno3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.32.236 netmask 255.255.255.0 broadcast 192.168.32.255
inet6 fe80::b605:5dff:fea2:8f04 prefixlen 64 scopeid 0x20<link>
ether b4:05:5d:a2:8f:04 txqueuelen 1000 (Ethernet)
RX packets 2058062 bytes 461793493 (440.4 MiB)
RX errors 0 dropped 12 overruns 0 frame 0
TX packets 5497087 bytes 367929054 (350.8 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
试一下
[root@localhost test]# ./ipv6_server 7838 1 fe80::b605:5dff:fea2:8f04
socket created
bind: Invalid argument
发现并不能用。同样,在客户端连接的时候,如果指定这个IP地址,也会报错
[root@localhost test]# ./ipv6_client fe80::b605:5dff:fea2:8f04 8888
创建socket成功
地址转化成功
Connect : Invalid argument
链路本地地址是一类特殊的IP地址,仅用于在网段内、同一广播域内的主机相互通信使用,这类主机可认为是不需要外部互联网服务的。其中IPv4的链路本地地址的范围是169.254.0.0/16,IPv6的链路本地地址的范围则是fe80::/10。在IPv4中,链路本地地址通常只用于网络接口没有外部的、有状态的IP地址的情况下(比如没有DHCP服务或其他地址配置失效的情况),即各种外部获取IP地址的途径失败时,主机会随机在169.254.0.0范围内随机选择一个地址(除两端的169.254.0.0/16和169.254.255.0/16,用作保留),用随机选的地址进行ACD(冲突地址检测),直到发现一个不发生冲突的随机地址则将其使用。这也是无状态地址自动配置的一种,IPv4的这个可以称作APIPA(自动专用IP寻址)。因为伪随机数的生成与种子相关,种子不变化时伪随机数基本也是固定的,所以有的主机会使用MAC地址作为伪随机数的生成种子,降低同一网段内随机选取的链路本地地址在ACD后发现冲突的概率。IPv6中的链路本地地址的生成是强制的。即主机如果支持IPv6协议栈则强制性的需要生成一个链路本地地址,这与IPv6本身的设计有关。IPv4中有广播的概念,域内广播到达路由器时是不会被转发到其他网段的,但IPv6没有广播的概念,IPv6尽可能用组播来代替IPv4的这一特性。如何实现的?IPv6中使用链路本地地址表示该报文不需要被跨网段转发,其最常见的应用即在于邻居发现协议,IPv6的ND协议是用于替代IPv4的ARP协议的从协议地址向硬件地址映射的功能。所以某种角度上看IPv6的链路本地地址其实就是一个用在二层的协议地址,与主机的MAC地址一一对应,效果上也与IPv4的链路本地地址一致,即只能在本网段内通信,不考虑互联网服务,尽管这不是它最常见的用法。
解决方法如下
发送方增加
my_addr.sin6_scope_id = 4;
接收方增加
dest.sin6_scope_id = 2;
后面的数字,可以通过接口
if_nametoindex (iface)
获取。
公网IP配置
ip addr add 2001:470:1f01:f52b::2/64 dev eno3
然后使用这个可聚合全球单播地址,就可以通信了。
server 可以使用 addr.sin6_addr= in6addr_any 进行bind 那么该端口的数据都能收到。 但是client
connect server的 fe80:xxxxxx 的本地链路地址会失败, 需要给server分配一个全球链路地址 比如
:2001:470:1f01:f52b::2/64 可以直接给server 手动添加 ip addr add
2001:470:1f01:f52b::2/64 dev eth0 然后client connect 到这个地址 server和
client 就可以正常进行通信。
参考资料
《ipv6是什么》
《IPv6(1)IPv6地址》
《网络工程师之IPv6(第3节,IPv6 地址分类)》
结束语
不知道国内有没有卖防核辐射的产品的公司,是不是股票要大涨了。
炸了人家的大桥,怕是要把大帝逼急了。
这种做法感觉也不是想善了了,对关键基础设施的破坏一旦成为冲突中的“常规手段”,所有国家面临的安全风险和全球紧张程度都将直线上升。
追梦永远不嫌远,勇士队的听到了吗