kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

nginx ipv6、TCP_DEFER_ACCEPT

curl 7.61.1

curl 7.61.1

TCP_DEFER_ACCEPT

1
2
3
server {
	listen  80 deferred;
	...

deferred instructs to use a deferred accept() (the TCP_DEFER_ACCEPT socket option) on Linux.

ipv6

1
2
3
server {
	listen  [::]:8080; # ipv6only=on;
	...

ipv6only=on|off this parameter (0.7.42) determines (via the IPV6_V6ONLY socket option) whether an IPv6 socket listening on a wildcard address [::] will accept only IPv6 connections or both IPv6 and IPv4 connections. This parameter is turned on by default. It can only be set once on start.

Prior to version 1.3.4, if this parameter was omitted then the operating system’s settings were in effect for the socket.

IPv6 socket

https://blog.csdn.net/u013401853/article/details/55002655

server_ip6.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define BUF_LEN 2048
#define PORT 8080

int main(int argc, char *argv[])
{
	int serv_sock = -1, client_sock = -1;
	socklen_t addr_len = 0;
	struct sockaddr_in6 local_addr = {0}, client_addr = {0};
	char buf[BUF_LEN] = {0};
	int err = -1;

	/* 建立socket */
	serv_sock = socket(PF_INET6, SOCK_STREAM, 0);
	if (-1 == serv_sock) {
		perror("socket error: ");
		return -1;
	}

	/* 填充地址结构 */
	local_addr.sin6_family = AF_INET6;
	local_addr.sin6_port = htons(PORT);
	local_addr.sin6_addr = in6addr_any;

	/* 绑定地址 */
	err = bind(serv_sock, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in6));
	if (-1 == err) {
		perror("bind error: ");
		close(serv_sock);
		return -1;
	}

	/* 监听 */
	err = listen(serv_sock, 5);
	if (-1 == err) {
		perror("listen error: ");
		close(serv_sock);
		return -1;
	}

	/* 循环等待客户连接请求 */
	while (1) {
		memset(&client_addr, 0x0, sizeof(client_addr));
		addr_len = sizeof(struct sockaddr_in6);
		client_sock = accept(serv_sock, (struct sockaddr *)&client_addr, &addr_len);
		if (-1 == client_sock) {
			perror("accept error:");
			close(serv_sock);
			return -1;
		}

		/* 转换client地址为字符串并打印 */
		inet_ntop(AF_INET6, &client_addr.sin6_addr, buf, BUF_LEN);
		printf("A clinet connected, ip: %s, port %d\n", buf, ntohs(client_addr.sin6_port));

		/* 接收消息 */
		memset(buf, 0x0, BUF_LEN);
		err = recv(client_sock, buf, BUF_LEN, 0);
		if (err < 0) {
			perror("recv error:");
			close(serv_sock);
			close(client_sock);
			return -1;
		}
		printf("recv %d bytes: %s\n", err, buf);

		/* 回送消息 */
		err = send(client_sock, buf, strlen(buf), 0);
		if (err < 0) {
			perror("send error:");
			close(serv_sock);
			close(client_sock);
			return -1;
		}

		/* 关闭这个client连接 */
		close(client_sock);
	}
	return 0;
}

client_ip6.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define BUF_LEN 2048
#define PORT 8080

int main(int argc, char *argv[])
{
	int sock = -1;
	socklen_t addr_len = 0;
	struct sockaddr_in6 serv_addr = {0};
	char buf[BUF_LEN] = {0};
	int err = -1;

	/* 建立socket */
	sock = socket(AF_INET6, SOCK_STREAM, 0);
	if (-1 == sock) {
		perror("socket error: ");
		return -1;
	}

	memset(&serv_addr, 0x0, sizeof(serv_addr));
	/* 填充地址结构 */
	serv_addr.sin6_family = AF_INET6;
	serv_addr.sin6_port = htons(PORT);

	//serv_addr.sin6_addr = in6addr_loopback;  /* 连接到环回地址 */

	//inet_pton(AF_INET6, "2002:da80:e000::1:1:9", &serv_addr.sin6_addr);

	//inet_pton(AF_INET6, "::ffff:c0a8:0208", &serv_addr.sin6_addr);
	//inet_pton(AF_INET6, "::c0a8:0205", &serv_addr.sin6_addr);

	// connect到链路本地地址,需要设置sin6_scope_id,用`ip addr show`获取
	serv_addr.sin6_scope_id = 2;
	inet_pton(AF_INET6, "fe80::a00:27ff:fea0:67d6", &serv_addr.sin6_addr);

	addr_len = sizeof(serv_addr);
	err = connect(sock, (struct sockaddr *)&serv_addr, addr_len);
	if (-1 == err) {
		perror("connect error:");
		close(sock);
		return -1;
	}

	/* 发送消息 */
	memset(buf, 0x0, BUF_LEN);
	snprintf(buf, BUF_LEN - 1, "hello server, I'm client\n");
	err = send(sock, buf, strlen(buf), 0);
	if (err < 0) {
		perror("send error:");
		close(sock);
		return -1;
	}

	/* 接收消息 */
	memset(buf, 0x0, BUF_LEN);
	err = recv(sock, buf, BUF_LEN, 0);
	if (err < 0) {
		perror("recv error:");
		close(sock);
		return -1;
	}
	printf("recv %d bytes: %s\n", err, buf);

	close(sock);

	return 0;
}

IPv6简介

https://segmentfault.com/a/1190000008794218

IPv6的优点

更大的地址空间

名字叫IPv6,但它的长度并不是64位,而是128位,总的地址空间大约为3.4*1038,一个亿是10的8次方,那么IPv6就有340万亿亿亿亿个地址(4个亿连一起),所以说给地球上的每一粒沙子分配一个IP地址不是在吹牛,是真可以。

可以参考这篇文章和这篇文章,里面提到地球上所有沙滩的沙子大约有7.5*1018粒,这个值跟IPv6的1038相差了很多个数量级,就算加上沙漠等其它的地方,IPv6的数量也足够覆盖它。

点到点通信更方便

IPv6完全有能力为联网的每个设备分配一个公网IP,于是我们可以不再需要NAT,从而非常方便的实现点到点的直接通信。

说好处之前,先了解一下NAT的缺点:
使用了NAT之后,每次通信都要做一次NAT转换,影响性能。
处于两个不同NAT网络内部的机器不能直接通信,他们之间的通信得依赖第三方的服务器,极大的限制了网络的连通性,同时所有的数据都会被第三方所监控。
为了支持NAT,很多网络协议变得很复杂,大大增加了网络的复杂性。

没有了NAT之后,当然上面的这些缺点也就没有了,同时会带来下面这些比较直观的好处:
更方便: 想象一下,每个电脑都有公网IP,你电脑出了点问题,找我帮忙看一下,只要把你的IP给我,我就可以连上去了,而我们现在的情况是,两个人都是内网IP,没法直接访问,非得用QQ共享桌面之类的软件。
更安全: 配合点到点的加密,让网络更安全,不给第三方监听的机会; 以网络聊天为例,通过使用点到点的聊天软件,就不用担心被人监听聊天记录了;同时访问家里的摄像头不再需要经过第三方服务器,不用担心给别人看直播了。

IP配置更方便

IPv6有一个功能叫Stateless Auto Configuration,简单点说,就是可以不借助DHCP服务器实现IP地址的分配,插上网线就能上网。

系统起来后,就会为每个网卡生成一个Link-Local的IP地址,简单点说就是一个固定的前缀加上mac地址,由于mac地址全球唯一,所以这样构成的IP地址是唯一的,有了这个地址后,就可以局域网进行通信了,但是这种地址路由器是不会转发的。

如果网络里有路由器; 系统会通过广播的方式问路由器,路由器会返回一个子网前缀,类似于IPv4里面的192.168.0.0/16,系统将子网前缀和mac地址组合起来,构成了一个唯一的IP地址,这个IP地址可以通过路由器路由。

也就是说,就算不做任何配置,系统启动起来后,网卡就一定会有IPv6地址,有了IPv6地址就可以通信。

当然IP地址也可以由DHCP6服务器来分配,这种方式分配叫做Stateful Auto Configuration。

局域网内更安全

由Neighbor Discovery代替了IPv4里面的ARP协议,没有ARP后,跟ARP相关的攻击就不存在了

路由更快

  • 跟IPv4不同,IPv6包头的字段长度是固定的,没有可选字段,所以路由器不需要检查IP包头是否包含可选字段。

  • IPv6包头里面没有checksum字段,不需要像IPv4那样每次TTL减1后都需要重新计算包头的checksum。

  • IPv6不支持在中途被分片和重组,即不能在路由器和防火墙上被分片,从而减轻了路由器的负担。

IPv6包头里面没有checksum,那么会不会不安全呢?如果数据传输的过程中损坏了怎么办呢?首先,现在的网络都比较好,出现损坏的情况很少;其次,就算损坏了,有两种情况,一种是被路由器丢弃或者发到了错误的主机,这种情况不会造成什么问题,因为IP层本来就不保证可靠的传输,而是由上面的传输层来保证(如TCP),另一种情况是接受方收到了数据包,但由于数据包受损,内容已经和发送方发出来的不一样了,这种情况也是交给上面的传输层协议处理,比如UDP、TCP,它们都有自己的校验码,完全有能力发现数据损坏的问题。

不允许路由器对IPv6包进行分片,那么怎么保证发送端不会发送太大的数据包呢?首先,IPv6要求入网链路至少能传输1280字节的IP包,如果出现不能传输1280字节IP包这种情况,需要链路层自己处理分片和重组的过程;其次,跟IPv4里面PMTUD(Path MTU Discovery)是可选的不同,在IPv6里面,PMTUD是一个非常重要且必须的功能;所以一般情况下发送小于等于1280字节的IP包肯定能到达目的地,加上现在大部分人都用以太网(MTU为1500,包含以太网的包头),绝大部分情况下一个包过去就能确定PMTU(Path MTU ),不会影响数据传输性能。

更安全

在设计IPv4的时候,根本没有考虑过安全问题。

而在设计IPv6的时候,安全问题作为一个很重要的方面被考虑进来了,尤其是端到端的安全,IPsec正是在这样的背景下被设计出来的,有了IPsec后,在IP层就能实现安全传输。

虽然IPsec也被引入到了IPv4,但由于IPsec连传输层的端口都进行了加密,导致IPsec碰到NAT网络的时候,会造成很多麻烦,虽然现在已经有了解决办法,但IPsec在IPv4网络里面还是受到诸多限制。

更好的QoS

IPv6的包头里面包含了一个叫做Flow Label的字段,专门为QoS服务。

更好的支持移动设备

移动网络要求设备能在不同的网络里面快速的切换,并且现有的通信不受切换的影响,在IPv6里面,有专门的协议Mobile IPv6 (MIPv6)来处理这个事情。

IPv6格式

这里不介绍报文的格式,只介绍IPv6地址的格式。

地址表示方式

IPv6地址的128位分成了由冒号分割的8段,每段2个字节16位,这16位由16进制表示,这里是一些例子,左边是完整的格式,右边是缩写格式:

1
2
3
4
5
6
7
8
完整的格式                  缩写格式
0000:0000:0000:0000:0000:0000:0000:0000   ::
0000:0000:0000:0000:0000:0000:0000:0001   ::1
FF02:0000:0000:0000:0000:0000:0000:0001   FF02::1
FC00:0001:A000:0B00:0000:0527:0127:00AB   FC00:1:A000:B00::527:127:AB
2001:0000:1111:000A:00B0:0000:9000:0200   2001:0:1111:A:B0::9000:200
2001:0DB8:0000:0000:ABCD:0000:0000:1234   2002:DB8::ABCD:0:0:1234 或者 2001:DB8:0:0:ABCD::1234
2001:0DB8:AAAA:0001:0000:0000:0000:0100   2001:DB8:AAAA:1::100

两条缩写规则:
用冒号分割的每段里面的前面的0可以省略掉,如:0001:可以缩写成:1:,:0000:可以缩写成:0:
如果冒号里面的是0的话,可以忽略掉(相邻的多个0可以一起忽略掉),直接写成两个冒号,如:0000:0000:可以被缩写成::

注意:如果地址中有多个连续为0的段,只能将其中的一个缩写成::,如果两个都缩写了,就不知道每个缩写了多少个0,这也是上面的表格中2001:0DB8:0000:0000:ABCD:0000:0000:1234被缩写成2002:DB8::ABCD:0:0:1234或者2001:DB8:0:0:ABCD::1234的原因,它不能被缩写成2001:DB8::ABCD::1234,一般的做法是哪种方法省略的0越多就用哪种。

网段表示方式

IPv6和IPv4一样,也有网段和子网的概念,在IPv6里面,表示子网号或者网段的时候,也是类似的方法,如:2001:0:0:CD30::/60,这个时候前面的地址只需要写前60位,后面的所有位都用::来缩写,类似于IPv4里面的192.168.0。0/16,不过要注意的是,这里2001:0:0:CD30::不能把前面的两个0也缩写,因为这样就不是一个合法的IPv6地址了。

IPv6地址类型

IPv6里面有三种地址类型;

  • Unicast: 单播地址,就是我们常用的地址,唯一标识一个网络接口

  • Anycast: 任意播(直译有点怪),一类特殊的IP地址,多个网络接口(不同的设备)都配上相同的地址,往这个地址发送数据的时候,路由器会只发往其中的一个接口,一般发往最近的那一个。(这个好像对实现负载均衡比较有用)

  • Multicast: 多播地址,代表一类unicast的集合,但往这个地址发送数据的时候,会将数据发给属于这个多播组的每个unicast地址。

IPv6里面没有类似于IPv4那样单独的广播概念,它的功能被包含在多播里面。

  • 本人对anycast和multicast不是特别了解,所以没法描述的很清楚。

IPv6地址分类

现有的IP地址被分配成如下几大类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
类型   前缀  IPv6表示方法
Unspecified   00...00 (128位)    ::/128
Loopback  00...01 (128位)    ::1/128
Multicast     11111111    FF00::/8
Link-Local unicast    1111111010  FE80::/10
Unique local address  1111110     FC00::/7
Global Unicast    所有其它

    全0的地址::/128为未定义地址,大家不要去使用

    除了最后一位是1,其它都是0的地址::1/128为本地环回地址,同IPv4里面的127.0.0.1

    FF00::/8这个网段的地址都是多播地址

    FE80::/10为Link-Local的单播地址,这类地址不能穿过路由器

    FC00::/7为本地的单播地址,可以穿过本地的路由器,但不能穿过外网的路由器,即只可以在本地使用,和IPv4里面的192.168.0.0/16相似

    全局的单播地址目前只有2000::/3开头的可以被申请使用,其它的都被预留了

预定义的多播地址

这里是两个常用的预定义的多播地址:

1
2
3
地址   含义
FF02:0:0:0:0:0:0:1    子网内的所有机器
FF02:0:0:0:0:0:0:2    子网内的所有路由器

后面有例子演示如何使用多播

子网的划分

IPv6要求所有的单播(unicast)地址的子网必须是64位的,即下面这种格式:

1
2
3
   |         64 bits         |         64 bits         |
   +-------------------------+-------------------------+
   |        subnet ID        |       interface ID      |   

如果子网的长度不是64位的话,会导致一些IPv6的功能不可用,详情请参考IPv6 Unicast Address Assignment Considerations。

Interface ID为Modified EUI-64格式,标准里面提供了如何将48位mac地址转换成EUI-64格式的方法。

IPv6标准要求单播地址的子网必须是64位的,主要是为了简化IPv6的管理,同时路由也方便,毕竟现在CPU都是64位的,如果子网号超过64位的话,会给路由造成一定的困难,同时64位的接口ID也比较容易存放一个UUID,比如可以容纳48位的mac地址,为Stateless Auto Configuration的地址分配提供了足够的空间。

64位的子网够用吗?64位的子网已经可以容纳264的设备了,相当于40亿个现在的IPv4地址空间的规模,实在是想不出还有哪种场合需要更大的子网。

64位的子网浪费吗?想想IPv4时代,几个人或者一群人通过NAT共享1个公网IP,而到了IPv6时代,这些人竟然可以拥有264个IP地址,想用几个用几个,为几个人分配一个64位的子网是不是有点浪费呢?其实谈不上浪费,IPv6的地址就是有那么多,大家都空着不用也是浪费,按道理64位的IP地址在可预见的将来已经够用了,而之所以采用128位IP加64位子网的方式,是因为能给我们的管理和使用方面带来很多的方便,如上面提到的便于路由和地址分配等。就算以后IP不够用了,再来放开子网位数的限制应该问题也不大。

想起了一句话: 等我有了钱,要装两条宽带,一条玩游戏,一条聊QQ。

Linux上配置IPv6

下面的所有例子都在ubuntu-server-x86_64 16.04下执行通过

现在的大部分Linux发行版默认情况下都启用了IPv6,如果没有,请参考发行版相关文档进行配置

1
2
3
#这里有输出,表示IPv6已结启用了
dev@ubuntu:~$ test -f /proc/net/if_inet6 && echo "IPv6 is already enabled"
IPv6 is already enabled

IPv6启用后,每个网卡都会有一个IPv6地址,如下:

1
2
3
4
5
6
7
8
9
10
dev@ubuntu:~$ ifconfig
enp0s3    Link encap:Ethernet  HWaddr 08:00:27:03:d0:e7
          inet addr:192.168.3.12  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe03:d0e7/64 Scope:Link
          ......

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          ......

这里lo的IPv6地址是环回地址::1,而enp0s3有一个“Scope:Link”的IPv6地址fe80::a00:27ff:fe03:d0e7,这个IP地址即上面说到的Link-local地址,它没法通过路由器,只能在子网内部使用。

由于IPv6对交换机没有要求,所以就算没有支持IPv6的路由器,我们也可以在本地局域网内试玩一下IPv6

通过ip命令就可以给网卡添加IPv6地址,和一个网卡只能有一个IPv4地址不同,一个网卡可以配置多个IPv6地址。

1
2
3
4
5
6
7
8
9
10
11
#添加一个global的地址
dev@ubuntu:~$ sudo ip -6 addr add 2001::1/64 dev enp0s3
#添加一个Unique local address地址
dev@ubuntu:~$ sudo ip -6 addr add fd00::1/64 dev enp0s3
dev@ubuntu:~$ ifconfig enp0s3
enp0s3    Link encap:Ethernet  HWaddr 08:00:27:03:d0:e7
          inet addr:192.168.3.12  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: fd00::1/64 Scope:Global
          inet6 addr: 2001::1/64 Scope:Global
          inet6 addr: fe80::a00:27ff:fe03:d0e7/64 Scope:Link
          ......

再来看看系统默认的路由表:

1
2
3
4
5
6
7
8
9
10
11
12
13
dev@ubuntu:~$ route -A inet6
Kernel IPv6 routing table
Destination                    Next Hop                   Flag Met Ref Use If
2001::/64                      ::                         U    256 0     0 enp0s3
fd00::/64                      ::                         U    256 0     0 enp0s3
fe80::/64                      ::                         U    256 1     3 enp0s3
::/0                           ::                         !n   -1  1   832 lo
::1/128                        ::                         Un   0   3    36 lo
2001::1/128                    ::                         Un   0   3     9 lo
fd00::1/128                    ::                         Un   0   2     5 lo
fe80::a00:27ff:fe03:d0e7/128   ::                         Un   0   3   193 lo
ff00::/8                       ::                         U    256 2    84 enp0s3
::/0                           ::                         !n   -1  1   832 lo

从“Next Hop”列可以看出,这里的所有网段都是本地接口可以直接到达的网段,不需要路由器转发。

使用IPv6

上节配置好了IPv6之后,我们这节来看看怎么使用这些地址

这里只用一台机器来演示怎么和自己通信,大家有条件的话可以试试两台机器之间通信,效果是一样的。

ping6

和IPv4里面的ping相对于的命令是ping6,对于不同类型的地址,ping的方式不一样(为了节省篇幅,示例中省略了ping成功时的输出):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ping lo的环回地址
dev@ubuntu:~$ ping6 ::1

#ping类型为“Scope:Global”的地址
dev@ubuntu:~$ ping6 fd00::1
dev@ubuntu:~$ ping6 2001::1


#ping类型为“Scope:Link”的地址
dev@ubuntu:~$ ping6 -I enp0s3 fe80::a00:27ff:fe03:d0e7

#ping一个多播(Multicast)地址,ff02::1代表子网中的所有机器
dev@ubuntu:~$ ping6 -I enp0s3 ff02::1
PING ff02::1(ff02::1) from fe80::a00:27ff:fe03:d0e7 enp0s3: 56 data bytes
64 bytes from fe80::a00:27ff:fe03:d0e7: icmp_seq=1 ttl=64 time=0.036 ms
64 bytes from fe80::3aea:a7ff:fe6c:ecff: icmp_seq=1 ttl=64 time=0.744 ms (DUP!)
64 bytes from fe80::188d:cbae:80d5:7a7a: icmp_seq=1 ttl=64 time=0.791 ms (DUP!)
......
#可以看到局域网中的其它机器回复的结果,这些IP都是其它机器的“Scope:Link”地址
#这里(DUP!)是由于ping多播地址时会收到多个回复,导致ping认为有重复的应答,其实是正常情况

#选择其中的任意一个,单独ping一下试试
dev@ubuntu:~$ ping6 -I enp0s3 fe80::188d:cbae:80d5:7a7a

#访问Link-local的地址的时候,除了-I参数外,我们可以直接这样访问
dev@ubuntu:~$ ping6 fe80::188d:cbae:80d5:7a7a%enp0s3

#或者根据enp0s3的id来访问
#获取enp0s3的id
dev@ubuntu:~$ grep enp0s3 /proc/net/if_inet6 | cut -d' ' -f2 | uniq
02
dev@ubuntu:~$ ping6 fe80::188d:cbae:80d5:7a7a%2

从上面可以看出,ping环回地址和global地址时,直接ping就可以了,而ping多播和Link-Local地址时,需要指定从哪个接口出去,这是因为机器上所有接口的Link-Local地址都属于同一个网段,当有多个接口时,根本没办法自动的判断应该从哪个接口出去。(不过从上面的路由表里面可以看出,在本地只有一个接口时,已经标识fe80::/64和ff00::/8可以从enp0s3口出去,不确定为什么在这种情况下,应用层的程序还要求指定接口名称,可能是为了保持统一吧,不管有几个接口,都一样的用法)。

注意: 如果是访问其它机器的link-local地址,-I参数和百分号的后面一定要指定本机出去的接口名称,而不是目的IP对应的接口名称

DNS

DNS里面有一个专门的IPv6类型,叫AAAA,查询的时候指定类型就可以了

1
2
3
4
5
6
7
8
9
10
11
#host命令默认情况下只查询A类地址,即IPv4地址
#指定-t AAAA即可查询域名的IPv6地址
#这里的结果显示,baidu.com还不支持IPv6,google.com已经支持了
dev@ubuntu:~$ host -t AAAA baidu.com
baidu.com has no AAAA record
dev@ubuntu:~$ host -t AAAA google.com
google.com has IPv6 address 2607:f8b0:400e:c04::65

#dig命令也是一样的参数
dev@ubuntu:~$ dig -t AAAA google.com
#这里省略输出结果,有点长

SSH

下面四种方式都可以登陆当前机器

1
2
3
4
dev@ubuntu:~$ ssh ::1   
dev@ubuntu:~$ ssh 2001::1
dev@ubuntu:~$ ssh fe80::a00:27ff:fe03:d0e7%enp0s3
dev@ubuntu:~$ ssh fe80::a00:27ff:fe03:d0e7%2

http

下面以curl来进行演示,如果有图形界面的浏览器的话,可以直接在浏览器里面输入同样的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#--------------------------第一个shell窗口----------------------
#准备一个支持IPv6的http服务器
dev@ubuntu:~$ sudo apt-get install php
dev@ubuntu:~$ mkdir web
dev@ubuntu:~$ echo "hello world!" > web/index.html
#启动http服务器,监听所有接口的8080端口
dev@ubuntu:~$ php -S [::]:8080 -t ./web/
PHP 7.0.15-0ubuntu0.16.04.4 Development Server started at Mon Mar 20 23:44:26 2017
Listening on http://[::]:8080
Document root is /home/dev/web
Press Ctrl-C to quit.

#--------------------------第二个shell窗口----------------------
#确认监听正确,这里:::8080就表示监听了所有IPv6和IPv4接口的8080端口
dev@ubuntu:~$ netstat -anp|grep 8080
tcp6       0      0 :::8080                 :::*                    LISTEN      13716/php

#先试试用IPv4的地址连过来,没有问题
dev@ubuntu:~$ curl http://127.0.0.1:8080/
hello world!

#IPv6的环回地址
dev@ubuntu:~$ curl http://[::1]:8080/
hello world!

#IPv6的global地址
dev@ubuntu:~$ curl http://[2001::1]:8080/
hello world!

#link-local地址
dev@ubuntu:~$ curl http://[fe80::a00:27ff:fe03:d0e7%enp0s3]:8080/
hello world!
dev@ubuntu:~$ curl http://[fe80::a00:27ff:fe03:d0e7%2]:8080/
hello world!

IPv6编程示例

这里以python代码为示例,写了一个UDP的服务器和客户端,演示如何同时支持IPv4和IPv6。(为了简化起见,代码里面没有做错误处理)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket
import sys

ip,port = sys.argv[1],int(sys.argv[2])

addrinfo = socket.getaddrinfo(ip, port, proto=socket.IPPROTO_UDP)[0]
sock = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
addr = addrinfo[4]
sock.bind(addr)

print("Listening on [{}]:{}...".format(addr[0], addr[1]))

while True:
    data, addr = sock.recvfrom(65535)
    print("Recvfrom [{}]:{}\t{}".format(addr[0], addr[1], data))
    sock.sendto(data, addr)

client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import socket
import sys

host,port = sys.argv[1],int(sys.argv[2])

addrinfos = socket.getaddrinfo(host, port, proto=socket.IPPROTO_UDP)
for addrinfo in addrinfos:
    sock = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
    sock.settimeout(2)
    data = b'hello'
    addr = addrinfo[4]
    sock.sendto(data, addr)
    print("Sendto   [{}]:{}\t{}".format(addr[0], addr[1], data))
    try:
        data, addr = sock.recvfrom(65535)
        print("Recvfrom [{}]:{}\t{}".format(addr[0], addr[1], data))
    except socket.timeout:
        print("timeout")

如果参数传入的是域名或者主机名,getaddrinfo函数可能返回多个IP,这时候客户端需要根据自己的应用特点选择一个或多个进行通信,在本例中是发送数据包给所有的IP。

getaddrinfo返回的IP列表里面的顺序是有讲究的,如果对这个很在意的话,请参考rfc6724,默认情况一般是IPv6的地址在前面,在Linux下还可以通过/etc/gai.conf来配置相关的顺序。

server使用示例

1
2
3
4
5
6
7
8
9
10
dev@ubuntu:~/ipv6$ python3 server.py :: 8000
Listening on [::]:8000...
dev@ubuntu:~/ipv6$ python3 server.py 0.0.0.0 8000
Listening on [0.0.0.0]:8000...
dev@ubuntu:~/ipv6$ python3 server.py 2001::1 8000
Listening on [2001::1]:8000...
dev@ubuntu:~/ipv6$ python3 server.py fe80::a00:27ff:fe03:d0e7%enp0s3 8000
Listening on [fe80::a00:27ff:fe03:d0e7%enp0s3]:8000...
dev@ubuntu:~/ipv6$ python3 server.py fe80::a00:27ff:fe03:d0e7%2 8000
Listening on [fe80::a00:27ff:fe03:d0e7%enp0s3]:8000...

server绑定所有IPv4和IPv6的接口, 然后client用不同的方式发包

1
2
3
4
5
6
7
8
9
10
dev@ubuntu:~/ipv6$ python3 server.py :: 8000
Listening on [::]:8000...
Recvfrom [fe80::a00:27ff:fe03:d0e7%enp0s3]:48033        b'hello'
Recvfrom [fe80::a00:27ff:fe03:d0e7%enp0s3]:50298        b'hello'
Recvfrom [2001::1]:60882        b'hello'
Recvfrom [::1]:44664    b'hello'
Recvfrom [::ffff:127.0.0.1]:46676       b'hello'
Recvfrom [::1]:55518    b'hello'
Recvfrom [::ffff:127.0.0.1]:35961       b'hello'
Recvfrom [fe80::a00:27ff:fe03:d0e7%enp0s3]:36281        b'hello'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
dev@ubuntu:~/ipv6$ python3 client.py fe80::a00:27ff:fe03:d0e7%enp0s3 8000
Sendto   [fe80::a00:27ff:fe03:d0e7%enp0s3]:8000 b'hello'
Recvfrom [fe80::a00:27ff:fe03:d0e7%enp0s3]:8000 b'hello'
dev@ubuntu:~/ipv6$ python3 client.py fe80::a00:27ff:fe03:d0e7%2 8000
Sendto   [fe80::a00:27ff:fe03:d0e7%enp0s3]:8000 b'hello'
Recvfrom [fe80::a00:27ff:fe03:d0e7%enp0s3]:8000 b'hello'
dev@ubuntu:~/ipv6$ python3 client.py 2001::1 8000
Sendto   [2001::1]:8000 b'hello'
Recvfrom [2001::1]:8000 b'hello'
dev@ubuntu:~/ipv6$ python3 client.py ::1 8000
Sendto   [::1]:8000     b'hello'
Recvfrom [::1]:8000     b'hello'
dev@ubuntu:~/ipv6$ python3 client.py 127.0.0.1 8000
Sendto   [127.0.0.1]:8000       b'hello'
Recvfrom [127.0.0.1]:8000       b'hello'
#由于localhost在/etc/hosts里面配置了两个IP,所以这里发了两个数据包,
#并且是先发IPv6的地址
dev@ubuntu:~/ipv6$ python3 client.py localhost 8000
Sendto   [::1]:8000     b'hello'
Recvfrom [::1]:8000     b'hello'
Sendto   [127.0.0.1]:8000       b'hello'
Recvfrom [127.0.0.1]:8000       b'hello'
#通过多播地址发给当前子网中的所有机器
dev@ubuntu:~/ipv6$ python3 client.py FF02:0:0:0:0:0:0:1%enp0s3 8000
Sendto   [ff02::1%enp0s3]:8000  b'hello'
Recvfrom [fe80::a00:27ff:fe03:d0e7%enp0s3]:8000 b'hello'