网络通信与信息安全之深入解析两台主机之间的通信过程和原理 Posted 2022-03-28 Serendipity·y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络通信与信息安全之深入解析两台主机之间的通信过程和原理相关的知识,希望对你有一定的参考价值。
一、前言
二、准备 Docker 环境
① 下载镜像
关于 Docker 的基础概念 (容器、镜像等),可以参考:Docker 入门教程 。 在启动 Docker 容器之前,需要先下载一个 Docker 镜像,这里使用 Ubuntu 系统镜像:
# docker pull < image>
docker pull ubuntu
② 首次启动
# docker run - it -- name < container> < image>
docker run - it -- name ubuntu ubuntu
参数说明:
-i:让容器的标准输入保持打开,从而能够接受主机输入的命令;
-t:为容器分配一个伪终端并绑定到容器的标准输入上,-i 和 -t 结合,可以在终端中和容器进行交互;
–name:为容器起一个名字,方便后续操作该容器,否则每次都需要查找容器的 ContainerID。 需要注意的是,每次 run 都会重新创建一个新的容器。
在 Docker 的各个命令中,< container_id> 和 < container_name> 、< image_id> 和 < image_name> 可以互换,本文统一使用 < container> 和 < image> 来指代这些参数。
③ 配置环境
apt- get update
apt- get install net- tools tcpdump iputils- ping
④ 提交镜像
如果不小心删除了容器,容器内的所有更改也将丢失,因此使用 commit 命令来保存容器中的更改:
# docker commit - m < message> -- author < author_info> < container> [ < repo> [ : < tag> ] ]
docker commit - m "Install packages" -- author "elonz" ubuntu ubuntu: latest
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest a5d22784e35b About a minute ago 108MB
# < image> 可以是上面的 REPOSITORY ( image_name) 或 IMAGE ID
docker image rm < image>
⑤ 删除容器
# docker rm < container>
docker rm ubuntu
docker container ls -- all
# docker run - it -- name < container> < image>
docker run - it -- name ubuntu ubuntu
⑥ 退出容器
exit
⑦ 再次启动容器
如果容器未启动 (Exited),执行 start 命令:
# docker start - i < container>
docker start - i ubuntu
docker exec - it < container> / bin/ bash
容器是否启动,可以通过 docker container ls --all 查看。
三、应用层
当通过诸如 http.Get(“http://www.baidu.com/”) 这样的 API 向服务器发送请求时,其底层实现无非以下几个过程:
通过操作系统提供的系统调用创建一个 socket 连接,这实际上是完成了 TCP 的三次握手过程;
通过 socket 连接以文本形式向服务端发送请求,在代码层面实际上是在向一个 socket 文件描述符写入数据,写入的数据就是一个 HTTP 请求。 可以直接在终端实现这个过程,只需要以下三行命令:
exec 3 < > / dev/ tcp/ www. baidu. com/ 80
printf "GET / HTTP/1.1\\r\\nHost: www.baidu.com\\r\\n\\r\\n" 1 > & 3
cat < & 3
① 建立连接
cd / dev/ fd && ll
total 0
dr- x-- -- -- 2 root root 0 March 18 13 : 06 . /
dr- xr- xr- x 9 root root 0 March 18 13 : 06 . . /
lrwx-- -- -- 1 root root 64 March 18 13 : 06 0 - > / dev/ pts/ 0
lrwx-- -- -- 1 root root 64 March 18 13 : 06 1 - > / dev/ pts/ 0
lrwx-- -- -- 1 root root 64 March 18 13 : 06 2 - > / dev/ pts/ 0
lrwx-- -- -- 1 root root 64 March 18 13 : 06 255 - > / dev/ pts/ 0
系统当前只有 /bin/bash 这一个进程,上面列出了该进程的 0、1、2、255 四个文件描述符:
文件描述符(file descriptor)是一个非负整数,从 0 开始,进程使用文件描述符来标识一个打开的文件;
系统为每一个进程维护了一个文件描述符表,表示该进程打开文件的记录表,而文件描述符实际上就是这张表的索引;当进程打开(open)或者新建(create)文件时,内核会在该进程的文件列表中新增一个表项,同时返回一个文件描述符,也就是新增表项的下标;
一般来说,每个进程最多可以打开 64 个文件,fd ∈ 0~63;在不同系统上,最多允许打开的文件个数不同,Linux 2.4.22 强制规定最多不能超过 1,048,576;
每个进程默认都有 3 个文件描述符:0 (stdin)、1 (stdout)、2 (stderr)。 执行以下命令,建立一个连接:
exec 3 < > / dev/ tcp/ www. baidu. com/ 80
该命令创建了一个指向 tcp://www.baidu.com:80 的可读写的 socket,绑定到当前进程的 3 号文件描述符:
exec fd< file:以只读的方式打开文件,并绑定到当前进程的 fd 号描述符;相应的,fd> 是以只写的方式打开文件;
打开 /dev/tcp/
h
o
s
t
/
host/
h o s t / port 文件实际上是建立连接并返回一个 socket,Linux 中一切皆文件,所以可以对这个 socket 读写。 执行以下命令,可以看到已经和 www.baidu.com 成功建立了 socket 连接:
cd / dev/ fd && ll # 或者:ll / proc/ $$/ fd
total 0
dr- x-- -- -- 2 root root 0 March 18 13 : 06 . /
dr- xr- xr- x 9 root root 0 March 18 13 : 06 . . /
lrwx-- -- -- 1 root root 64 March 18 13 : 08 0 - > / dev/ pts/ 0
lrwx-- -- -- 1 root root 64 March 18 13 : 08 1 - > / dev/ pts/ 0
lrwx-- -- -- 1 root root 64 March 18 13 : 08 2 - > / dev/ pts/ 0
lrwx-- -- -- 1 root root 64 March 18 13 : 11 255 - > / dev/ pts/ 0
lrwx-- -- -- 1 root root 64 March 18 17 : 25 3 - > 'socket:[54134]' # 绑定在 3 号描述符
② 发送请求
向 www.baidu.com 发送一个 GET 请求,只需要向 3 号文件描述符写入请求报文 (格式):
printf "GET / HTTP/1.1\\r\\nHost: www.baidu.com\\r\\n\\r\\n" 1 > & 3
说明:> 3:重定向到名为 3 的文件;>& 3:重定向到 3 号文件描述符。
③ 读取响应
cat < & 3
< ! DOCTYPE html >
< ! -- STATUS OK -- > < html>
. . .
< / html>
④ 关闭连接
# 关闭输入连接:exec fd< & - ;关闭输出连接:exec fd> & -
exec 3 < & - && exec 3 > & -
这样就在 bash 中实现了 http.Get(“http://www.baidu.com/”)。
四、传输层
客户端使用 socket(), connect() 等系统调用来和远程主机进行通信。在底层,socket() 负责分配资源,connect() 实现了 TCP 的三次握手过程。 Socket 通过 <源 IP、源 Port、目的 IP、目的 Port> 的四元组来区分 (实际上还有协议,TCP 或 UDP),只要有一处不同,就是不同的 socket。因此,尽管 TCP 支持的端口号最多为 65535 个,但是每台机器理论上可以建立无数个 socket 连接。比如 HTTP 服务器只消耗一个 80 端口号,但可以和不同 IP:Port 的客户端建立连接,实际受限于操作系统的内存大小。 使用 netstat 命令可以查看当前系统中的所有 socket:
exec 3 < > / dev/ tcp/ www. baidu. com/ 80 # 在容器中手动创建一个 socket
exec 4 < > / dev/ tcp/ www. bing. com/ 80 # 同上,只是为了演示
netstat - natp
Active Internet connections ( servers and established)
Proto Recv - Q Send - Q Local Address Foreign Address State PID / Program name
tcp 0 0 192.168 . 1.2 : 36384 110.242 . 68.4 : 80 ESTABLISHED 1 / bash
tcp 0 0 192.168 . 1.2 : 44960 202.89 . 233.100 : 80 ESTABLISHED 1 / bash
TCP 连接状态 含义 SYN_SENT 发送了连接请求,等待远端的确认(三次握手第 1 步的结果) ESTABLISHED socket 已经建立了连接
TCP 连接状态 含义 LISTEN 监听来自远程应用的 TCP 连接请求 SYN_RECEIVED 收到了连接请求并发送了确认报文,等待最终的确认(三次握手第 2 步的结果) ESTABLISHED socket 已经建立了连接,这是数据传输阶段的状态
TCP 连接状态 含义 FIN_WAIT1 发送了连接终止请求,等待确认,通常持续时间较短 FIN_WAIT2 发送了连接终止请求并收到了远程的确认,等待远程 TCP 的连接终止请求,这个状态表明远程在收到此 socket 的连接终止请求后,没有立刻关闭它的 socket CLOSING 发送了连接终止请求后,收到了远程的连接终止请求,正在等待远程对连接终止请求的确认,这个状态表明双方同时进入关闭状态 TIME_WAIT 等待足够的时间,以确保远程 TCP 收到其连接终止请求的确认 CLOSED socket 已经没有连接状态
TCP 连接状态 含义 CLOSE_WAIT 收到了远程的连接终止请求,正在等待本地的应用程序发出连接终止请求 LAST_ACK 等待先前发送的连接终止请求的确认,只有在发送连接终止请求前先收到了远程的连接终止请求时,才会进入此状态 CLOSED socket 已经没有连接状态
五、网络层
网络层的功能是路由与寻址,数据包在网络层是一跳一跳地传输的,从源节点到下一个节点,直到目的节点,形成一个链表式的结构。 当数据包到达网络中的一个节点时,该节点会检查数据包的目的 IP 地址,如果不是自己的 IP 地址,就根据路由表决定将数据包发送给哪个网关。
① 路由表
电脑、手机、路由集等都可以视为网络层的一个节点 (或一台主机),每个节点都有一个路由表。网络层的节点通过路由表来选择下一跳地址 (next hop address)。 Ubuntu 系统可以通过以下命令查看路由表:
route - n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0 . 0.0 192.168 . 1.1 0.0 . 0.0 UG 0 0 0 eth0
192.168 . 1.0 0.0 . 0.0 255.255 . 255.0 U 0 0 0 eth0
路由表由一组规则组成,每条规则包含以下字段:
Destination:目的地址,可以是主机地址或网络地址,常见的是网络地址;
当 Destination 为 0.0.0.0 时,其 Gateway 为当前局域网的路由器 / 网关的 IP 地址。当 Gateway 为 0.0.0.0 时,表示目的机器和当前主机位于同一个局域网内,它们互相连接,任何数据包都不需要路由,可以直接通过 MAC 地址发送到目的机器上。 通过 Destination 和 Genmask 可以计算出目的地址集。例如对于下面的表项,其含义为“所有目的 IP 地址在 192.168.1.0 ~ 192.168.1.255 范围内的数据包,都应该发给 0.0.0.0 网关“。
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168 . 1.0 0.0 . 0.0 255.255 . 255.0 U 0 0 0 eth0
当 Genmask 为 255.255.255.255 时,代表这条规则的 Destination 不再是一个网络地址,而是一个网络中的一台特定主机,这样的规则可能对应一条点对点信道 (Point to Point Tunnel)。 路由表中的规则可以手动指定,也可以通过路由协议来交换周围网络的拓扑信息、动态更新。
② 路由决策过程
当一个节点收到一个数据包时,会根据路由表来找到下一跳的地址。具体而言,系统会遍历路由表的每个表项,然后将目的 IP 和子网掩码 Genmask 作二进制与运算,得到网络地址,再判断这个地址和表项的 Destination 是否匹配:
如果只有一个匹配项,直接将数据包发送给该表项的网关 Gateway;
如果有多个匹配项,则选择子网掩码最长的那个规则,然后发送给对应的网关;
上面最后一种情况实际上不会出现,因为路由表中包含了下面这条规则:
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0 . 0.0 192.168 . 1.1 0.0 . 0.0 UG 0 0 0 eth0
任何目的 IP 和全 0 的 Genmask 作与运算,一定会得到全 0 的 Destination,这保证所有未知的目的 IP 都会发送给当前局域网的默认网关 (比如路由器),由后者决定下一跳的地址。
③ 案例分析
ping www.baidu.com 的路由决策过程; ping 局域网的另一台主机 的路由决策过程; 对于以下的路由表,路由器会如何转发目标 IP 为 128.75.43.16 和 192.12.17.10 的数据包?
Destination Gateway Genmask
128.75 . 43.0 A 255.255 . 255.0
128.75 . 43.0 B 255.255 . 255.128
192.12 . 17.5 C 255.255 . 255.255
default D 0.0 . 0.0
结果分析:
目标 IP 位于外部网络,默认会发给本局域网的路由器,路由器连接外部网络,知道该如何转发数据包,例如交给更高一级的运营商网关;
同局域网的主机交换数据不需要网关或路由器,直接发给交换机,交换机根据 Mac 地址发送到对应的主机;
A and D,128.75.43.16 匹配了前两条规则,相应的 Destination 均为 128.75.43.0,数据包会发送给具有最长子网掩码的网关,192.12.17.10 没有匹配任何 Destination,数据包发送给默认网关。
六、数据链路层
① ARP 协议
当网络层选择一个特定 IP 的主机作为下一跳时,如何将数据包正确的发送给该主机?这里需要在数据包外面加上下一跳的硬件地址 (MAC),在数据包的整个传输过程中,目的 MAC 地址每一跳都在变,但目的 IP 地址不变。 如何根据 IP 地址找到相对应的 MAC 地址?这需要使用 ARP 协议 (Address Resolution Protocol,地址解析协议)。每台主机都设有一个 ARP 高速缓存表,记录本局域网内各主机的 IP 地址到 MAC 地址的映射。 执行以下命令可以查看主机的 ARP 缓存表:
arp - a
localhost ( 192.168 . 1.1 ) at bc: 5f: f6: df: d8: 19 on en0 ifscope [ ethernet]
localhost ( 192.168 . 1.102 ) at 14 : 7d: da: 32 : 8d: 17 on en0 ifscope [ ethernet]
ARP 高速缓存是自动更新的,当主机 A 向本局域网的主机 B 发送数据包时,如果 ARP 高速缓存中没有主机 B 的硬件地址,就会自动运行 ARP 协议,找出 B 的硬件地址,并更新高速缓存,过程如下:
主机 A 在局域网内广播一个 ARP 请求分组,内容为:“我的 IP 是 IP_A,硬件地址是 MAC_A,我想知道 IP 地址为 IP_B 的主机的硬件地址“;
主机 B 接受到此请求分组后,如果要查询的 IP 地址和自己的 IP 地址一致,就将主机 A 的 IP 地址和 MAC 地址的对应关系记录到自己的 ARP 缓存表中,同时会发送一个 ARP 应答 (单播),内容为:“我的 IP 地址是 IP_B,硬件地址是 MAC_B”;
其他主机的 IP 地址和要查询的 IP 地址不一致,因此都丢弃此请求分组;
主机 A 收到 B 的 ARP 响应分组后,同样将主机 B 的 IP 地址和 MAC 地址的对应关系记录到自己的 ARP 缓存表中。 ARP 协议只用于局域网中,不同局域网之间通过 IP 协议进行通信。
② ARP 协议抓包
首先,需要另一个终端来监听容器内的网络请求,假设已经通过 docker start 启动了一个容器,使用 docker exec 命令来创建一个新的终端会话:
docker exec - it ubuntu / bin/ bash # 宿主机执行
为了便于表示,将一开始 docker start 创建的容器终端记为 A,docker exec 创建的记为 B,在终端 B 内执行以下命令,监听网络请求:
tcpdump - nn - i eth0 port 80 or arp
随后,在终端 A 中执行以下命令,触发 ARP 协议更新:
# arp - d < ip> && ping www. baidu. com
arp - d 192.168 . 1.1 && ping www. baidu. com
其中,arp -d 命令可以删除一条 ARP 映射记录,这里需要将 替换为容器的网关 IP 地址,有许多方法可以获取容器的网关地址:
[容器内] 执行 route -n 命令,查看 Destination 为 0.0.0.0 时对应的 Gateway;
[宿主机] 执行 docker network inspect bridge,查看 IPAM - Config - Gateway。 在终端 A 中有以下输出,表示 ping 命令执行成功:
PING www. a. shifen. com ( 110.242 . 68.3 ) 56 ( 84 ) bytes of data.
64 bytes from 110.242 . 68.3 ( 110.242 . 68.3 ) : icmp_seq= 1 ttl= 37 time= 19.7 ms
64 bytes from 110.242 . 68.3 ( 110.242 . 68.3 ) : icmp_seq= 2 ttl= 37 time= 22.7 ms
64 bytes from 110.242 . 68.3 ( 110.242 . 68.3 ) : icmp_seq= 3 ttl= 37 time= 21.8 ms
tcpdump: verbose output suppressed, use - v or - vv for full protocol decode
listening on eth0, link- type EN10MB ( Ethernet ) , capture size 262144 bytes
17 : 04 : 43.847963 ARP , Request who- has 192.168 . 1.1 tell 192.168 . 1.2 , length 28
17 : 04 : 43.848058 ARP , Reply 192.168 . 1.1 is - at 02 : 43 : 21 : c4: 75 : 58 , length 28
17 : 04 : 53.009573 ARP , Request who- has 192.168 . 1.2 tell 192.168 . 1.1 , length 28
17 : 04 : 53.009746 ARP , Reply 192.168 . 1.2 is - at 02 : 43 : ac: 11 : 00 : 02 , length 28
可以看到,这里有两次 ARP 请求与应答,第一次是容器 (192.168.1.2) 广播查询网关 (192.168.1.1) 的 MAC 地址,第二次是网关 (192.168.1.1) 广播查询容器 (192.168.1.2) 的 MAC 地址。
七、两台主机的通信过程
① 不同局域网的两台主机
以主机 ping 一个域名为例,过程如下:
[主机] [应用层] 通过 DNS 协议获取域名的 IP 地址;
[主机] [网络层] 构造 IP 数据包,源 IP 为本机 IP,目的 IP 为域名 IP;
[主机] [网络层] 根据路由表,选择下一跳的 IP 地址,即当前局域网的网关;
[主机] [链路层] 根据 ARP 表,查找网关 IP 的 MAC 地址;在 IP 数据包外面包一层 MAC 地址;
[局域网] 根据 MAC 地址,上一步的报文最终会发送到当前局域网的网关;
[网关] [网络层] 网关查看数据包的目的 IP 地址,重复上述 2~3 步,继续发给下一跳;
[互联网] 中间经过若干个下一跳主机,最终数据包发送到域名所在的网络中心的网关;
[网关] [网络层] 网络中心的网关查看数据包的目的 IP 地址,根据路由表发现目的 IP 对应的 Gateway 为 0.0.0.0,这表明目的机器和自己位于同一个局域网内;
[网关] [链路层] 根据 ARP 表,查找目的 IP 的 MAC 地址,构造链路层报文;
[局域网] 根据 MAC 地址,上一步的报文最终会发送到目的主机;
[目的主机] [网络层] 目的主机查看数据包中的目的 IP,发现是给自己的,解析其内容,过程结束。
② 同局域网内的两台主机
两台主机通过网线、网桥或者交换机连接,就构成了一个局域网。网桥或交换机的作用是连接多台主机,隔离不同的端口,每个端口形成单独的冲突域。当主机连接到网桥或交换机端口的时候,这些设备会要求主机上报 MAC 地址,并在设备内保存 MAC 地址与端口的对应关系。 同局域网内的两台主机进行通信时,只需要根据 ARP 协议获取目的主机的 MAC 地址,构造链路层报文。报文会经过网桥或交换机,后两者根据目的 MAC 地址,在 MAC 地址表里查询目的端口,然后将报文从目的端口转发给对应的主机。 注意:
交换机是链路层的设备,主要根据 MAC 地址进行转发、隔离冲突域;不具有路由功能,不记录路由表,这类设备也称为二层交换机,如果只使用二层交换机、不使用路由器来构建局域网,需要为交换机和每台主机分配同属于一个子网的静态 IP。
路由器工作在 OSI 的第三层网络层,记录路由表,并以此控制数据传输过程。
有些交换机也具有路由功能,记录了路由表,能够简化路由过程、实现高速转发,这类设备也称为三层交换机。
③ 同局域网内的两台主机,目的主机有多个 IP
问题:如果目的主机 B 为自己新增了一个 IP,同局域网的主机 A ping 主机 B 的这个 IP 能 ping 通吗?答案是不能,原因是主机 A ping 主机 B 时,根据路由表会将报文发给默认网关,但是网关的路由表里并没有主机 B 新增加的 IP 信息。 可以做实验验证一下:分别启动两个容器 A、B(参数 --cap-add NET_ADMIN:打开网络配置权限):
docker run - it -- name ubuntu -- cap- add NET_ADMIN ubuntu # 容器 A
docker run - it -- name ubuntu_2 -- cap- add NET_ADMIN ubuntu # 容器 B
在容器 B 内执行以下命令,新增一个 IP 地址 192.168.1.55:
ifconfig lo: 3 192.168 . 1.55 / 16
查看容器 B 的 IP 配置,可以看到新增了一个 lo:3 接口:
ifconfig
eth0: flags= 4163 < UP , BROADCAST , RUNNING , MULTICAST > mtu 1500 # 这里是默认 IP
inet 192.168 . 1.2 netmask 255.255 . 0.0 broadcast 192.168 . 255.255
ether 02 : 42 : ac: 11 : 00 : 03 txqueuelen 0 ( Ethernet )
RX packets 9 bytes 726 ( 726.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 ( 0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags= 73 < UP , LOOPBACK , RUNNING > mtu 65536
inet 127.0 . 0.1 netmask 255.0 . 0.0
loop txqueuelen 1000 ( Local Loopback )
RX packets 0 bytes 0 ( 0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 ( 0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: 3 : flags= 73 < UP , LOOPBACK , RUNNING > mtu 65536 # 这里是新增的 IP
inet 192.168 . 1.55 netmask 255.255 . 0.0
loop txqueuelen 1000 ( Local Loopback )
在容器 A 内尝试 ping 192.168.1.55,发现无法 ping 通,解决办法是修改容器 A 的路由表,执行以下命令,手动新增一条规则:
# route add - host < destination> gw < gateway>
route add - host 192.168 . 1.55 gw 192.168 . 1.2
其中,destination 参数是容器 B 新增的 IP 地址;gateway 参数是容器 B 的默认 IP 地址,也就是上面 ifconfig 命令输出的 eth0 接口的 IP 地址,这条规则的含义是“所有目的 IP 是 192.168.1.55 的数据包都发给 192.168.1.2”。 容器 A 内执行 route -n,查看路由表:
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0 网络通信与信息安全之深入解析TCP与UDP传输协议
网络通信与信息安全之深入解析TCP连接中如何确定客户端的端口号
网络中两台主机的通信过程(TCP)
加密协议及其相关算法简介
网闸原理
浏览器从输入到输出的过程与原理五之网络通信和三次握手