kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

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'

setterm 防止黑屏

https://unix.stackexchange.com/questions/8056/disable-screen-blanking-on-text-console

防止黑屏

1
2
3
setterm -blank 0 -powersave off

# cat /sys/module/kernel/parameters/consoleblank

名字

setterm - 设置终端属性

概要

setterm [选项]

描述

setterm向终端写一个字符串到标准输出,调用终端的特定功能。在虚拟终端上使用,将会改变虚拟终端的输出特性。不支持的选项将被忽略。

选项

对于布尔选项(on或off),默认设置为on。

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
   简洁8色如下:黑色,红色,绿色,黄色,蓝色,洋红色,青色,或白色
   black, red, green, yellow, blue, magenta, cyan, or white.
   16色是8色加上灰度或明暗,在红色、绿色、黄色、蓝色、洋红色、青色或白色之后加上灰度或明暗
   red, green, yellow, blue, magenta, cyan, or white + grey 或 bright
   各种颜色选项可以独立设置,其中设置多个模式的结果(例如,下划线和-半明亮)是硬件相关的。
   -term 终端名字
        覆盖环境变量TERM.
   -reset 显示终端重置字符串,它通常将终端重新设置为电源的状态??(测试未见任何效果)
   -initialize 清空屏幕。
   -cursor [on|off] 显示或关闭光标(测试时,没有效果)
   -repeat [on|off] 只在虚拟主机上有效:键盘打开或关闭(测试时,显示不支持)
   -appcursorkeys [on|off] 只在虚拟主机上有效
        将光标键应用程序模式设置为on或off. 
   -linewrap [on|off] (virtual consoles only)
        自动换行或关闭。
   -default:将终端的呈现选项设置为默认值。
   -foreground 8-color|default 设置前景文本颜色
   -background 8-color|default 设置背景文本颜色。
   -ulcolor 16-color (virtual consoles only)为加下划线的字符设置颜色。
   -hbcolor 16-color (virtual consoles only)设置半明字符的颜色。
   -inversescreen [on|off] (virtual consoles only)颠倒的屏幕颜色。前台和后台交换,下划线和半亮交换。
   -bold [on|off] 打开或关闭粗体(额外亮度)模式
   -half-bright [on|off]将昏暗(半亮度)模式开启或关闭
   -blink [on|off]开启或关闭闪烁模式
   -reverse [on|off]打开或关闭反向视频模式,字符和字符背景交换颜色(-inversescreen是全屏交换)
   -underline [on|off]在开启或关闭状态下显示下划线模式
   -store 存储终端当前的呈现选项
   -clear all:同命令clear
   -clear rest:测试时报参数错误
   -tabs [tab1 tab2 tab3 ...] 不带参数,测试结果如下。带参数没效果。
            root@myzr:~# setterm -tabs
            10        20        30        40        50        60        70
            12345678901234567890123456789012345678901234567890123456789012345678901234567890
            T      T       T       T       T       T       T       T       T       T      T
   -clrtabs [tab1 tab2 tab3 ...] 测试时报终端不支持:setterm: terminal xterm does not support --clrtabs
   -regtabs [1-160] 测试时报终端不支持:setterm: terminal xterm does not support --regtabs
   -blank [0-60|force|poke] 设置不活动的时间间隔,在几分钟内,之后屏幕将自动变白(如果可用的话,使用APM)
        force:即使按键被按下,也要保持屏幕空白。
        poke:开启屏幕
   -dump [1-NR_CONS]  将给定虚拟控制台(带有属性)的快照写入-file选项中指定的文件,覆盖该文件,默认文件是screen.dump
   -append [1-NR_CONS] 类似-dump,但是将其附加到快照文件,而不是重写它。
   -file dump文件名
   -msg [on|off] 启用或禁用发送内核printk()消息到控制台。
   -msglevel 1-8 设置内核打印等级。
   -powersave on|vsync 将监视器放入VESA vsync挂起模式。测试无效
   -powersave hsync 将监视器放入VESA hsync挂起模式。测试无效
   -powersave powerdown 将监视器放入VESA关闭模式。测试无效
   -powersave [off]节能模式。测试无效
   -powerdown [0-60]测试无效
   -blength [0-2000]:以毫秒为间隔设置钟的持续时间,没有参数,默认是0。测试时不支持
   -bfreq [freqnumber] 将钟频率设置为赫兹,没有参数,默认是0。测试时不支持
   -version 输出版本信息
   -help  输出帮助信息

timeout 命令

命令简介

运行指定的命令,如果在指定时间后仍在运行,则杀死该进程。用来控制程序运行的时间。

使用方法

1
2
3
timeout [选项] 数字[后缀] 命令 [参数]...

后缀 s 代表秒(默认值), m 代表分, h 代表小时, d 代表天。

选项详解

长选项必须使用的参数对于短选项时也是必需使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  -s, --signal=信号
        指定在超时时发送的信号。信号可以是类似"HUP"的信号名或是信号数。
        查看"kill -l"以获得信号列表
      --help        显示此帮助信息并退出
      --version        显示版本信息并退出
``

如果程序超时则退出状态数为124,否则返回程序退出状态。

如果没有指定信号则默认为TERM 信号。TERM 信号在进程没有捕获此信号时杀死进程。

对于另一些进程可能需要使用KILL (9)信号,当然此信号不能被捕获。

#### 示例
timeout 10 top

``` 解释:如过top命令在10秒内结束,则平安结束,运行超过10秒,将被强行kill掉。

进程通信--pipe管道

Linux函数原型

1
2
3
#include <unistd.h>

int pipe(int filedes[2]);

filedes[0]用于读出数据,读取时必须关闭写入端,即close(filedes[1]);

filedes[1]用于写入数据,写入时必须关闭读取端,即close(filedes[0])。

程序实例:

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
#include <stdio.h>
#include <unistd.h>

#define MAXLINE   20
int main(void)
{
	int n;
	int fd[2];
	pid_t pid;
	char line[MAXLINE];

	if (pipe(fd) < 0) {                /* 先建立管道得到一对文件描述符 */
		return -1;
	}

	if ((pid = fork()) < 0)                /* 父进程把文件描述符复制给子进程 */
		return -2;
	else if(pid > 0) {             /* 父进程写 */
		close(fd[0]);             /* 关闭读描述符 */
		write(fd[1], "\nhello world\n", 14);
	} else {                          /* 子进程读 */
		close(fd[1]);             /* 关闭写端 */
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	}

	return 0;
}