Introduction
在我翻译文章的时候,总是遭到网络协议的粗暴的性对待,我想是时候了结了!
本篇文章没有必要按顺序看,毕竟我就不是按顺序写的.我先写的是TCP部分🐿🐿
OSI Model
cloudflare这篇文章写的还行,link
不过其实没必要分那么细,通常来说分为4层就够了:
自下而上的看,首先是DATALINK Layer
简要来讲,作用有2:
- 数据传播的形式是电平,所以需要把高低电平转换为2进制数据
- 规定数据的传输形式,数据的基本单位是帧(frame),以MAC地址区分不同的设备,有线传输的协议是以太网
Ethernet
,无线传输的协议是Wi-Fi
数据链路层只能用于局域网内传输.
Network Layer
网络层:
网络层主机的标识符为IP,至于为什么不用MAC地址,是因为MAC地址只是一个唯一的序列,但是IP地址是根据区域划分的
IP地址的格式:
123.123.234.234
,4段,每段的范围0-255
,因为每段只能占一个字节
一个IP地址分为这两部分,网络号用来标识主机位于的子网,主机号是局域网内设备的标识
而数据传输,也叫路由,是将数据从一个区域传输到另一个区域,也就是从一个子网传输到另一个子网
至于为什么要分两部分,举个相似的例子:身份证,身份证前6位是地区编号,网络号也起着相似的作用
计算网络号的方式是
IP & netmask
,netmask是子网掩码
transport layout
传输层:
网络层实现主机与主机之间的通信,但是一个主机可能运行不止一个进程,所以使用端口来区分不同进程的数据包
比如说,你在运行一个QQ,QQ占用的端口是4000,那么地址为
192.168.111.1:4000
就是发送给QQ的数据包传输层主要有两个协议,TCP/UDP
TCP用于可靠的传输数据,首先在传输方与接收方之间建立连接,把数据拆分为多个数据包,然后按顺序传输数据,如果过程有丢包还要重传
UDP只发送数据包,不管数据包是否到达目的地,LOL用的就是UDP
application layout
应用层
TCP/IP协议簇已经实现了进程与进程之间的可靠通信,而应用层就是在这之上,规定一些具有特定结构的数据的传输协议
对于传输层来说,数据就只是二进制流,但是在应用层,数据被协议赋予了特定的意义,比如说HTTP把数据分为
head & body
,head
用来存储一些额外的信息,比如域名,cookie之类的,而body
就是真正需要的数据了
HTTP HTTPS SMTP
等都是应用层协议
Ethernet
以太网主要用于局域网内传输数据,设备的唯一标识是MAC地址(48bit)
在以太网协议中,数据的格式为帧(frame).
Ethernet 2 Frame
(以太 II 帧,也称作DIX以太网):
type段占俩字节,实际上就只有两种:
0x0080
,表示IPV40x0806
,表示这是一个ARP包(ARP协议的讲解在IP中,概括的说是根据IP获得MAC地址)
HUB
最简单的通讯方式是两台主机直接通过线缆连接,当设备多了起来,需要一个中继设备(hub)间接转发数据包,使用同一个hub的主机组成局域网(LAN).
主机与hub的端口相连,也就是一个主机对应一个hub的端口,主机间的通讯,是通过一种广播(broadcast)机制
集线器在接收到数据包之后,并不会直接将包传输给符合MAC地址的主机,而是将包发向所有端口,然后主机检查自己的MAC地址是否与包中的一致(数据帧的头部的Destination),如果一直就接收包,不一致就忽略包.
hub的问题有二:
- 多个主机同时需要传输数据时会造成堵塞
- 半双工,同一时间通信双方数据传输只能有一个方向,不能边读边写
为了解决这些问题而出现了交换机.
Switch
参考:link
交换机是全双工的,通俗的讲就是可以边吃边拉,通信端可以一边读取数据,一边发出数据.
交换机内部维护一张MAC地址表,记录MAC地址与端口的对应关系.
辣么,在忽略VLAN(virtual LAN)的情况下,传输数据包的规则如下:
- 查询MAC表,是否记录了数据包的源MAC地址
- 存在,
continue
- 不存在,表中插入一条记录:
MAC-Port
- 存在,
- 查询MAC表,是否记录了数据包的目标MAC地址
- 存在,单播转发
- 不存在,执行泛洪(相当于广播).各个主机接收到数据包检查目的MAC地址,是的话就笑纳,不是的话就婉拒.
有一些点需要注意:
- 交换机只记录数据帧的源MAC与端口的对应关系,至于为什么不记录目标MAC与端口的对应关系,我暂且蒙在交换机的ass里(可能是没有必要?反正你迟早要发送数据包).
- 交换机层面有4种帧:广播帧,未知帧,同端帧(源mac和目的mac指向同一端口,实际上就是俩mac相等),异端帧.
- 广播帧是目的MAC地址全为F的情况,也就是
FF:FF:FF:FF:FF:FF
,这种情况下交换机会向所有端口转发,主机看到mac地址全为F
就直接把帧笑纳了- 当出现未知帧,也就是MAC表中找不到对应端口的帧,会进行泄洪,泄洪相当于广播,只不过地址不是全
F
,主机会比对mac地址决定是否接收该帧- 异端帧会进行单播转发
- 同端帧被丢弃,你可能会想,那我平时怎么访问
127.0.0.1
的?蒽,我猜测回环地址127.0.0.1
是一个虚拟网卡,网卡驱动会特殊处理,不过我毕竟妹写过驱动,所以这是瞎猜.- 这里只是对交换机的抽象化的描述,具体的情形要比这复杂的多.请读者自行查阅.
Swittie
简单来说,就是主机通过MAC地址,IP地址,协议之类进行分组.
主要作用捏,就是防止目标mac位置,泛洪造成堵塞.分组之后泛洪只在一个VLAN中进行,不会广播到所有设备,高端一点的说法就是广播隔离(好吧,偷自维基百科).
在之前说过,主机在发现帧的目的MAC与自己不一致,就会丢弃这个包,那有没有不丢弃的方法呢,有的.
有线网卡可以开启混杂(promisc)模式,接收所有数据帧,对应的无线网卡有监听(monitor)模式.
用airmon-ng提醒邻居提高wifi强度(小心真人快打)的时候,就要开启网卡监听模式.
IP
IP,IP,You mom’s P.
Concept
虽然IP说到底就是一个根据区域划分的唯一标识符,但初接触时还是会遇到一些不太友好的概念.
IP
,Internet Protocol,位于传输层之下,链路层之上.主要的作用是根据源IP与目的IP实现两台主机之间的数据传输,为此定义了数据结构和寻址方法.
wiki:
数据在IP互联网中传送时会封装为数据包.网际协议的独特之处在于:在报文交换网络中主机在传输数据之前,无须与先前未曾通信过的目的主机预先创建好特定的”通路”.互联网协议提供了不可靠的数据包传输机制(也称”尽力而为”或”尽最大努力交付”);也就是说,它不保证数据能准确的传输.数据包在到达的时候可能已经损坏,顺序错乱(与其它一起传送的报文相比),产生冗余包,或者全部丢失.如果应用需要保证可靠性,一般需要采取其他的方法,例如利用IP的上层协议控制(TCP).
IP Address
32位整数,每8位为一段,每段范围0-255
netmask
子网掩码
是一个和IP地址类似的数据,用来将IP分成网络段和主机段.前面说过,IP地址是根据区域划分的,一片区域有多个主机,这些主机同属于一个子网,所以,标识IP地址对应的区域实际上是IP所属的子网.
netmask
就是用来计算IP所对应的子网的(也就是网段)
IP & netmask
= 网段 按位与运算比如说 ip=174.185.123.111,netmast=255.255.255.0
那么网段就是 174.185.123.0
把IP比作身份证是很贴切的,比如身份证的前6位是地区编码,但IP的网段长度不是固定的,所以需要子网掩码.
gate
网关(说实话,我觉得网管更贴切).网关其实是用来路由的设备的IP地址,所谓的路由,抽象的说就是将数据包传输到目的IP的主机上.
那么,用自己的电脑路由不行吗?当然是行的(比如openwrt,笔者的树莓派就用来当路由器),但是有专门用来路由的设备 - router(路由器).
这样,连接到同一路由器的主机发送的数据包都由路由器统一转发,然后路由器又转发到另一个路由器… 最后到达目的地.
dns
Domain Name System,域名解析系统.
因为IP不太好记,就用域名比如
www.google.com
代替google服务器的IP地址.实际访问时域名会从DNS服务器查询对应的IP地址.
使用
whois
命令可以查询域名的一些信息.
IP数据包结构体:
位偏移 | 0–3 | 4–7 | 8–13 | 14-15 | 16–18 | 19–31 | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 版本 | 首部长度 | 区分服务 | 显式拥塞通告 | 全长 | |||||||||||||||||||||||||||
32 | 标识符 | 标志 | 分片偏移 | |||||||||||||||||||||||||||||
64 | 存活时间 | 协议 | 首部检验和 | |||||||||||||||||||||||||||||
96 | 源IP地址 | |||||||||||||||||||||||||||||||
128 | 目的IP地址 | |||||||||||||||||||||||||||||||
160 | 选项(如首部长度>5) | |||||||||||||||||||||||||||||||
160 or 192+ |
数据 |
网络号 | 主机号 | 是否可以作为源地址 | 是否可以作为目的地址 | 备注/描述 |
---|---|---|---|---|
全为0 | 全为0 | 允许 | 禁止 | 表示本网主机 |
全为0 | Host ID | 允许 | 禁止 | 表示特定主机 |
全为1 | 全为1 | 禁止 | 允许 | 定向广播地址 |
127 | 任意合法的值 | 允许 | 允许 | 迂回地址,用于本地测试 |
Network ID | 全为1 | 禁止 | 允许 | 直接广播地址 |
这里介绍一下直接广播地址,广播地址的IP
为网段内的最后一个地址,该数据包会转发给网段内所有主机.
ARP
Address Resolution Protocol,地址解析协议.
TCP/IP
协议簇根据IP:Port
确定主机上的特定进程,只需要知道对方的ip和端口就可以传输数据.
数据包的封装是自上而下的,tcp
包到ip层被封装成ip
包,然后到数据链路层,而Ethernet协议需要知道目标ip的MAC地址才可以传输数据包(ip包此时作为以太网数据包的数据部分),如果在ARP
表中没有ip对应的MAC地址(比如重启或者第一次与该网段通讯),主机会暂存数据包.
于是,需要一种协议来获取ip所对应的MAC地址,没错,就是ARP协议.
ARP
协议位于IP层之下,目标IP(一般是IP协议,当然也有可能是别的)地址包含在数据部分.通过发送一个ARP包来获得目标IP的MAC地址,数据包的目标MAC地址为FF:FF:FF:FF:FF:FF
,其实是利用了广播机制,(在数据链路层)交换机会将数据包转发给目标IP所在的局域网内所有主机(的网卡),然后拆包传递给上层协议(没错,封装是自上而下的,拆包是自下而上的),在IP层,如果主机的IP地址与数据包的目标IP相同,那么会给源IP回应一个arp包,这样就得到了MAC地址(只要你不太笨的话,应该就能知道封装数据包的时候会在以太网数据帧的头部写入源MAC),然后源主机会把MAC缓存在arp表中,之后再传输数据时直接查表就行辣.
ARP协议对应的以太网数据帧:
- MAC地址全为1(你不能连这个都不理解吧,6个
FF
实际上是48个1的十六进制格式,笨蛋) - 数据帧类型为
0x0806
长度(bit) | 48 | 48 | 16 | 16 | 16 | 8 | 8 | 16 | 48 | 32 | 48 | 32 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
数据类型 | 目标以太网地址 | 源以太网地址 | 帧类型 | 硬件类型 | 协议类型 | 硬件地址长度 | 协议地址长度 | 操作码 | 源硬件地址 | 源协议地址 | 目标硬件地址 | 目标协议地址 (IP在这!) |
组成 | 14字节 以太网首部 | 28字节 ARP请求/应答 |
TCP
tcp,全称 Transmission Control Protocol
,传输控制协议.
wiki:
在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层.不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换.
Connect Establishment
TCP头部结构:
偏移 | 字节 | 0 | 1 | 2 | 3 | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
字节 | 比特 | 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 | 29 | 30 | 31 |
0 | 0 | 来源连接端口 | 目的连接端口 | ||||||||||||||||||||||||||||||
4 | 32 | 序列号码 | |||||||||||||||||||||||||||||||
8 | 64 | 确认号码(当ACK设置) | |||||||||||||||||||||||||||||||
12 | 96 | 资料偏移(TCP头部长度) | 保留 0 0 0 |
N S |
C W R |
E C E |
U R G |
A C K |
P S H |
R S T |
S Y N |
F I N |
窗口大小 | ||||||||||||||||||||
16 | 128 | Checksum 校验和 | 紧急指针(当URG设置) | ||||||||||||||||||||||||||||||
20 ... |
160 ... |
选项(如果资料偏移 > 5,需要在结尾添加0.) ... |
虽然TCP连接是双向的,数据可以双向传输,不过根据实际情况,不妨把想要建立TCP连接的叫做cli,监听TCP连接的叫做ser
创建连接的过程叫做三次握手
,双方一共发送三个数据包.
- cli想要与ser建立连接,发送一个
SYN
数据包,数据包中包含一个由cli生成的随机数A
,A是序号(请先忽略序号这个概念) - ser接收
SYN
包,给cli发送SYN/ACK
包,包含一个由ser生成的随机数B
以及确认号A+1
- cli接收
SYN/ACK
包,给ser发送ACK
包,包含确认号B+1
建立连接的过程很可能不稳定,比如网络延迟之类的.如果在步骤2之后ser没有接收到cli发送的ACK包,这时处于一个中间态,既没有成功也没有失败(抄自维基百科),ser会假定之前发送的SYN/ACK丢失了,于是重新发送 SYN/ACK
包,如果接收到cli发送的ACK
,辣么建立连接.
但如果一直没有接收到cli的ACK,ser会在重复发送几次ACK/SYN
数据包之后结束此次连接.在Linux中,一般最多重复5次,每次发送间隔翻倍,1s,2s,4s,8s,16s,总共浪费的时间为1+2+4+8+16+32(在最后一次发送包之后还要等待32s才确认超时)=63s
三次握手的目的是为了防止已经失效的建立连接的ACK
报文(比如因为网络延迟,在cli接收不到应答之后关闭连接,这时cli已经不想建立连接了)被服务器接收然后建立连接,因为是三步验证,所以ser在接收到失效的ACK报文后发送的用于验证的SYN/ACK
给cli,cli不会给ser发送ACK
包确认建立连接,而ser在重复发送 SYN/ACK
之后始终不会接收到ACK
报文,然后ser关闭连接,此过程只有63s,不会浪费太多资源
如果只有两次握手,即第三次的cli验证报文不需要发送就可以建立连接,那么假设昨天cli给ser发送了一个SYN
数据包但是因为网络滞留了一天,所以今天才被ser接收到,ser
随即发送SYN/ACK
数据包,并且认为连接已经确定,但cli忽视SYN/ACK
包,也不会发送数据给ser,但是ser这时候一直在等待cli发送数据,这样就会浪费资源,而在三次握手的情况下,此连接会因为cli一直没有响应而在63s后被ser断开.
举一个简单的例子:
A给B打电话,但是B今天不在家所以一直没接听,所以A给B留了个语音留言(SYN滞留).第二天B接听了A的语音留言,回拨给A,但是此时A不会接B的电话,因为A今天不想与B通话,B在电话无人接听之后又打了几次,最后结束通话.
那么,cli何以肯定ser发送过来的SYN/ACK
无效呢?这就要用到序号SN(sequence number)
与确认号ACKN(acknowledge number)
了,还记得在过程12生成的随机数A和B吗?简单来说,因为SYN/ACK
包的确认号是无效的,cli拒绝建立连接.
Data Transfer
序号和确认号的概念对于某些笨蛋来说相当绕,但其实它们两个只是一个相对的概念,相对发送数据的主体.
在tcp层面上,数据被叫做字节流stream
,数据是按字节顺序传输的,对于流中的每一个字节,都有一个确定的序号,理解成数组下标就行了,只不过数组的起始下标不是0,而是一个随即数X
序号是发送的字节流的起始字节的编号,确认号是对方发送的字节流的起始字节的编号.TCP接收者在接收到一定数量连续字节流# TLS后才发送确认.
确认号的作用是告诉数据发送方上一个包已经收到,确认号实际上是希望接收的下一个数据包的序号.
序号是32位无符号整形,范围0-65536
,在增大到65536后会回绕到0.
TPC使用序号并把数据分组,保证数据即使乱序传输仍能以正确顺序拼接数据块,也无需担心丢包问题(丢了再重传就是了).
Retransmission
基于重复累计ACK的重传
wiki:
如果一个包(不妨设它的序号是100,即该包始于第100字节)丢失,接收方就不能确认这个包及其以后的包,因为采用了累计ack.接收方在收到100以后的包时,发出对包含第99字节的包的确认.这种重复确认是包丢失的信号.发送方如果收到3次对同一个包的确认,就重传最后一个未被确认的包.阈值设为3被证实可以减少乱序包导致的无作用的重传(spurious retransmission)现象.选择性确认(SACK)的使用能明确反馈哪个包收到了,极大改善了TCP重传必要的包的能力.
超时重传
wiki:
发送方使用一个保守估计的时间作为收到数据包的确认的超时上限.如果超过这个上限仍未收到确认包,发送方将重传这个数据包.每当发送方收到确认包后,会重置这个重传定时器.典型地,定时器的值设定为
进一步,如果重传定时器被触发,仍然没有收到确认包,定时器的值将被设为前次值的二倍(直到特定阈值).
细心的你一定发现了,之前说过三次握手过程中一直没有接收到cli的ACK包,ser重新发送SYN/ACK的时间间隔将每次翻倍
这是由于存在一类通过欺骗发送者使其重传多次,进而压垮接收者的攻击,而使用前述的定时器策略可以避免此类中间人攻击方式的拒绝服务攻击.
Example
重点是:数据接收方每接收到一个数据包,就会向数据发送方发送一个ACK
包表示自己接收到数据包了.但是如果有包丢失,即使接收到后续的包,ACK
的确认号始终是丢失的包的序号.
注意,
ACK
包长度为0,也就是说ACK不占用序号(毕竟它不被用来传输数据,只是告诉数据发送方接收到了数据包)
wiki讲的很好,我就不重复造轮子了:
- 发送方首先发送第一个包含
序号为1
(实际上是一个随机数,这里为了简化设为1)和200字节
数据的TCP报文段给接收方.接收方以一个 没有数据的TCP报文段(也就是ACK
包) 来回复(只含报头),用确认号201
来表示已完全收到并请求下一个报文段(而201,也是下一个报文段的序号,上一个数据包的起始序号为1,长度200,序号范围为1-200
). - 发送方然后发送第二个包含
序列号为201,长度为100字节
的数据的TCP报文段给接收方.正常情况下,接收方回复ACK包,用确认号301(201+100)
来表示已完全收到并请求下一个报文段.发送接收这样继续下去. - 然而当这些数据包都是相连的情况下,接收方没有必要每一次都回应.比如,他收到第1到5条TCP报文段,只需回应第五条就行了.如果
第3条
TCP报文段被丢失了,就算尽管他收到了第4和5条,然而他只能回应第2条
(接收第4,5条不会发送ACK包回应,因为数据流断开了),即ACK包的确认号
始终是301
. - 发送方在发送了第3条以后,没能收到回应,因此当时钟(timer)过时(expire)时,他重发第三条.(每次发送者发送一条TCP报文段后,都会再次启动一次时钟:RTT).
- 这次第三条被成功接收,接收方可以直接确认第5条,因为4,5两条已收到.
checksum
checksum-校验和,可以校验一些简单的数据包传输错误.
校验和计算设计TCP头部和数据段,还有伪头部(96位),伪头部是一个虚拟的数据结构,实际上TCP并不包含这一部分,伪头部包括源IP,目标IP,协议和TCP长度,要包含伪头部的原因是校验的时候可以检查数据包是否被正确路由
typedef struct PsuedoHeader{
uint32_t source;
uint32_t dest;
uint8_t reserved; //保留字节,全部置0
uint8_t protocol;
uint16_t length;
}; //一共12字节
校验和计算:将伪头部,TCP头部和数据部分求和,然后求反码(1的补码)
校验和会被装入TCP头部,接收方接收到数据报文后会进行校验,校验不需要再重复求一边校验和了,只需要计算PsuedoHeader+TCP_Header+data+checksum
,如果所有位都为1,说明数据没问题.
Sweetie
对于一些需要可靠传输的应用来说,TCP很有用,但是TCP并不适用与所有场合.
举个简单的例子,网络游戏,比如LOL,用的是UDP,虽然UDP只管传不管到没到,但这对LOL来说是可以忍受的,丢包只会让画面有些卡顿
但如果是TCP,首先TCP会将数据流分组,或者说缓存了一定大小的数据包才发送,这表示如果你按了个技能,这个数据太小导致TCP没有发送它而是和后续数据一块发,这显然是不能接受的,因为网络游戏需要即时反馈,不过可以通过TCP_NODELAY
设置成直接发送.
但是还有一个问题,TCP丢包后会重发,这表示即使有那么一点点不关键的画面丢失了,TCP仍然会重新发包,然后接受端就得一直等丢失的包重传过来,1s,2s,4s… 作为一个玩家,我显然是不会玩这么猪比的游戏的,我宁愿接受UDP的漂移,也不要TCP的卡顿63s后的流畅画面
这篇文章讲的还行:link
Connect Termination
结束已经建立TCP连接,这个过程叫做4次挥手👋
因为TCP的连接是双向的,所以任何一端在数据传输完毕后都可以请求关闭TCP连接.
但一端的数据传输完毕另一端不一定结束,因此关闭过程需要保证如果被动关闭的那一端仍有数据要发送,主动关闭的一端仍有接收数据的能力.
在下例中,cli主动请求关闭TCP连接,ser被动关闭
cli发送
FIN
报文给ser,序号为X
,此时cli停止发送数据,但仍能接收数据,进入FIN_WAIT1
状态ser接收
FIN
报文,发送ACK
报文,序号为Y
,确认号为X+1
,ser进入CLOSE_WAIT
状态,cli在接收ACK
报文后进入FIN_WAIT2
状态在
CLOSE_WAIT
状态期间,ser仍能发送数据,在确认所有数据被cli接收之后,发送FIN/ACK
报文,序号为P
(在此期间可能发送了一些数据),确认号为X+1
,ser进入LAST_ACK
状态cli接收
FIN/ACK
报文,发送ACK
报文,序号为X+1
,确认号为P+1
,然后cli进入TIME_WAIT
状态,该状态会持续2MSL
(从发出ACK
报文,到cli再接收到ser报文的时间,即一个报文来回一次的时间),如果在此期间cli没有接收到ser的报文,关闭连接.ser接收到ACK
报文,关闭连接.之所以要等待
2MSL
时间:如果第4步cli发送的ACK
包丢失了,ser会以为cli没接收到第3步的FIN/ACK
报文,然后重新发送,cli在2MSL
时间内接收到新的FIN/ACK
报文,会重复第4步,重新计时.为什么一定要确保ser接收到
ACK
报文捏,因为此时ser处于LAST_ACK
状态,需要接收到cli的ACK
报文之后才结束连接.
TLDR:
握手首先会释放一端到另一端的连接,然后主动释放的一端等待被动释放的一端传输完所有数据,之后双方互相确认,结束TCP连接.
A:我说完了
FIN
FIN_WAIT1
B:蒽,我知道你说完了
ACK
CLOSE_WAIT
,但我还没说完B: . . . . . . (把话说完)
CLOSE_WAIT
B:蒽,我也说完了
FIN/ACK
LAST_ACK
A:蒽,我知道你也说完了,但你个beyond最好是说完了
ACK
TIME_WAIT
B:挂了
A:等待
2MSL
TIME_WAIT
A:蒽,这B雀食是说完了,挂了
Life of a Packet
现在,必要的前提知识都已经具备,让我们来体验一下一个数据包的一生.
小提示:
基于
Ethernet
,可以实现局域网内的数据传输.基于
IP
,可以实现不同局域网内的主机间的数据传输.基于
TCP
,可以实现不同局域网内的主机间的端口间的的可靠数据传输.数据包的封装顺序是从上而下的(上层协议的数据包传递给下层,作为下一层协议的数据部分,然后在头部写入该层协议的信息,再传递给下一层…),处理是自上而下的,下层的数据被解包,数据部分传递给上一层.
假设访问google,使用post
请求(通常,get
请求不会携带数据),首先要获得google.com的IP地址,查询IP使用的是DNS协议,这里不再赘述,通过dig查看IP:
好辣,现在已经知道目标IP,那么开始传输数据包吧!
数据包将从应用层开始封装,这里使用了HTTP协议.封装而成的数据包长这样:
通常,Head
包含一些有关请求的信息,而body是数据.
请求头信息:
然后,数据包被传输到传输层,使用TCP/UDP协议,这里使用TCP.
如果未与目标主机建立连接,会首先建立连接,这里假设连接已经建立了,本机端口为10086,目标端口为80.在传输层数据包被封装成TCP包.
然后把数据包传递到IP层,前面说已经建立过tcp连接,所以肯定知道MAC了,那么通过查询ARP表是可以获取google.com
的某台服务器的MAC
地址的,辣么,封装成IP包:
接下来就到数据链路层辣,数据链路层会将数据包封装成两个部分,定长为14b
的头部和44-1500b
的数据和4b
的CRC校验码(校验码就是一种hash,用来检验数据包是否完好无损).因为这是个IP包而不是arp包,所以头部的type=0x0080
,值得一提的是,http协议的默认端口就是80🐁🐁
好,自此数据封装结束,进入物理层,我们假定你有一个路由器并且已经连上网,那么,数据包会通过网卡发送给网关(也就是路由器的IP,不过这里其实是路由器的MAC).
为什么要发送给网关捏?蒽,你的电脑没有路由功能,路由器有.
路由器会检查该数据包是否属于该子网,如果不属于,就要路由,所谓路由,就是计算从一个子网到另一个子网的最佳路径,这里不做赘述,总之,路由器会将数据包转发到下一个路由器,直到到达目的IP的子网内.
数据包是自上而下传输的,在物理层,该子网的交换机(或者路由器)会检查头部的目的MAC
地址,然后将该数据包传输到MAC
对应的端口.
好了,目标主机已经接收到数据包了,解包过程是自下而上的,和封包相反.
解包会将数据部分传递给上层协议,ethernet->ip->tcp->http
,最后获取到http body
的数据.
自此,一个数据包已经完成了它的任务,这里只是一个简化的过程,具体的过程要复杂的多,感兴趣的读者可自行了解.
TLS
推荐一个网站:link
TLS,全称Transport Layer Security
,前身为SSL(Secure Socket Layer)
在了解这种技术之前,不妨先思考一下此技术为什么被开发出来?在万维网诞生之初,网络通信是明文的,这意味着使用中间人攻击就可以获取所有通信数据,并且篡改数据.因此,对于一些需要隐私的通信场景,例如网购,需要一种能够加密通信内容并且能够验证内容未被篡改的手段.
wiki:
传输层安全性协议(英语:Transport Layer Security,缩写:TLS)及其前身安全套接层(英语:Secure Sockets Layer,缩写:SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障.网景公司(Netscape)在1994年推出首版网页浏览器-网景导航者时,推出HTTPS协议,以SSL进行加密,这是SSL的起源.IETF将SSL进行标准化,1999年公布TLS 1.0标准文件(RFC 2246).随后又公布TLS 1.1(RFC 4346,2006年)、TLS 1.2(RFC 5246,2008年)和TLS 1.3(RFC 8446,2018年).在浏览器、电子邮件、即时通信、VoIP、网络传真等应用程序中,广泛使用这个协议.许多网站,如Google、Facebook、Wikipedia等也以这个协议来创建安全连线,发送资料.目前已成为互联网上保密通信的工业标准.
SSL包含记录层(Record Layer)和传输层,记录层协议确定传输层数据的封装格式.传输层安全协议使用X.509认证,之后利用非对称加密演算来对通信方做身份认证,之后交换对称密钥作为会谈密钥(Session key).这个会谈密钥是用来将通信两方交换的资料做加密,保证两个应用间通信的保密性和可靠性,使客户与服务器应用之间的通信不被攻击者窃听.
Encryption
对称加密使用相同的密钥加密和解密数据,但是想直接用于网络通讯是不现实的:通讯双方首先要协商密钥,然后才能加密通讯.但是协商密钥是明文进行的,如果被窃听,那么数据加密就毫无意义了.但是,又必须在加密通信之前协商密钥,一种解决措施是以一个可靠的方式(显然不是明文传输)交换密钥,比如说我写封信给你(🐥🐥).
为了解决如上问题,需要一种能够堂而皇之的交换密钥的加密方法 - 非对称加密.
非对称加密使用两个密钥,公钥和私钥.其中一把密钥加密后的数据能够通过另一把钥匙解密,并且公钥和私钥是不等的.
一般来说使用公钥加密,私钥解密.公钥可以公开,私钥需要自己保存.
在交换了密钥之后,持有公钥的一方使用公钥加密数据,而窃听者因为没有私钥所以无法获取明文,从而保证了通信的安全.
此外,还需要一种方法来验证数据在传输过程中完整且未被篡改👇.
data signature
比如foo和bar通信:
- foo持有公钥,bar持有私钥
- foo需要一种方法来验证bar传输过来的数据没有问题
那么通信的过程是:
- bar要传输的数据是d1,用hash函数生成d1的hash值hd1(常见算法:MD5,HMAC,SHA1,SHA256),然后bar使用私钥加密hd1
- foo收到数据d1和数字签名hd1,使用公钥解密hd1,然后和d1的hash值比较,如果相同则数据完好.
窃听者是无法篡改数字签名的,就算他用公钥解密了数字签名然后篡改,他也没有私钥对数字签名重新加密,所以数字签名能够很好的保证数据完好.
但其实还有一个问题,就是中间人攻击 Man in the Middle
👇👇
Man in the Middle
还是以foo和bar来举例,现在有一个坏蛋pee想要捣乱.
- pee有bar的公钥,bar有私钥,
现在foo和bar之间还未交换密钥
- pee能够监听foo和bar之间的通信
执行攻击的过程:
- foo想要bar的公钥,但是通信被pee截取了,于是pee把自己的公钥给了foo
- foo使用pee的公钥加密发送给bar的数据,因此数据可以被pee解密,然后pee再用bar的公钥加密后发给bar
- bar向foo发送的数据会被pee截取,pee使用bar的公钥解密,修改数据然后用自己的私钥加密发送给foo
- foo使用pee的公钥解密,以为数据没问题.
TLDR:在通信双方交换公钥之前,中间人用自己的公钥掉包原公钥,而通信端没有手段验证公钥的合法性.
因此,为了解决中间人攻击的问题,需要一个可靠的被信任的第三方机构提供验证公钥合法性的手段 - 证书.
颁发证书的机构叫证书机构(我承认这是一句废话),证书机构会给网站颁发证书的过程(当然,一般是要收钱的) - 网站拥有者把自己的个人信息(比如邮箱,网站域名…)和公钥一起提交给证书机构,证书机构用自己的私钥加密的数据就是证书.
现在,bar给foo发送公钥的时候还会顺带发送自己的证书,foo使用证书机构的公钥解密证书,然后验证自己收到的公钥是否和证书记录的一致,如果一致,那就没问题.
虽然pee可以监听请求,但是他对证书无能为力.首先,他不能篡改证书,毕竟他没有证书机构的私钥对篡改后的数据重新加密,其次他也没有证书证明foo访问的网站的公钥是pee的公钥(毕竟网站不是他的,证书机构不至于如此猪比).所以现在中间人攻击就被解决辣!
总结:证书机构真的是躺着赚钱,个beyond个人证书都得上千,我支持firefox和chrome制裁这些流氓证书机构.
Procedure
以上为TLS的基础知识,现在来具体了解TLS(SSL).
TLS架构:
重要的是握手协议和记录协议:
握手协议用来交换密钥,记录协议用来加密/解密数据.
Handshake
SSL:
一共13个包,第一次建立SSL至少需要9个包.
TLS1.2:
以下内容根据TLS1.2
协商阶段:
cli发送ClientHello包给ser,数据包包含:最高支持的TLS版本,一个随机数,客户端支持的密码套件和支持的压缩方法.(如果客户端是在恢复之前已建立的TLS会话,会发送一个session ID.)如果客户端可以使用数据层协议协商(一个TLS扩展),那么数据包还包含支持的应用层协议列表,比如HTTP.
ser响应一个ServerHello包,包含从客户端提供的方法里选择的TLS版本,一个随机数,密码套件和压缩方法.(如果同意恢复,会发送一个session ID).TLS是cli和ser共同支持的最高的版本.
ser发送Certificate信息(根据选择的密码套件,服务器可能忽略这一步).
ser发送ServerKeyExchange信息(同上),之前决定的密码套件所需要交换的数据包含在这一步.(比如DH算法就需要这一步)
ser发送ServerHelloDone信息,表示握手协商阶段完成.
cli响应ClientKeyExchange信息,包含cli生成的
pre master secret(预备主密钥)
,预备主密钥使用ser的公钥加密.cli和ser使用随机数和
pre master secret
计算出一个共通密钥 - master secret(cli和ser计算出来的master secret是相同的).所有其他的密钥(比如对称密钥)都衍生自master secret
和cli&ser生成的随机数(随机数使用pseudorandom函数生成).
cli发送
ChangeCipherSpec
记录给ser,本质上是告诉ser:现在我发送给你的数据将会被验证和加密ser发送
ChangeCipherSpec
给cli:现在我发送给你的数据也会被验证和加密.应用阶段:这时握手阶段已经结束,应用层协议已经确定.应用层的数据会被加密和验证(使用数字签名以确保信息未被更改).
由
master secret
衍生的密钥中:
sysmmetric key
,对称密钥用于加密和解密信息(没错,虽然也可以用非对称加密,但是非对称加密的代价较大,对称加密比较省资源).MAC key
,数据验证码密钥,用来生成数字签名MAC(Message Authentication Code)的密钥.
Data Transfer
简要介绍一下建立TLS连接后的数据传输过程.
现在有两个密钥,对称密钥和MAC密钥.
ser首先生成数字签名(MAC),使用MAC key计算出数据的MAC(或者说HMAC,hash mac,本质上是数据的hash值),然后用对称密钥加密数据.然后把数据发送给cli.
cli使用对称密钥解密数据,然后也使用MAC key计算出明文的MAC,然后和发送过来的MAC比较,如果相同则说明数据没问题.
因为中间人没有MAC密钥,所以即使修改了数据也无法生成数据对应的MAC.
SNI
SNI(Server Name Indication)是TLS的一个扩展协议.
基于域名的虚拟主机(即多个dns指向同一个IP,主机根据请求的域名提供不同的服务)允许多个DNS主机名由同一IP地址上的单个主机(通常为Web服务器)托管.为了实现这一点,服务器使用客户端请求的域名作为协议的一部分(对于HTTP,名称显示在请求头头中).但是,当使用HTTPS时,TLS握手发生在服务器看到任何HTTP头之前.因此,服务器不可能使用HTTP主机头中的信息来决定呈现哪个证书,并且因此只有由同一证书覆盖的名称才能由同一IP地址提供.
客户端在SNI扩展中发送要连接的主机名称,作为TLS协商的一部分.这使服务器能够提前选择正确的域名,并向浏览器提供相应TLS证书.从而,具有单个IP地址的服务器可以在获取公共证书不现实的情况下提供一组域名的TLS连接.
SNI在2003年6月的RFC 3546,《传输层安全(TLS)扩展》中加入到IETF的Internet RFCs中.最新版本的标准是RFC 6066.
TLDR: 客户端在和服务器建立TLS连接的时候会在 ClientHello 消息中放入客户端所请求的域名.
由于SNI信息并未加密,审查者可以识别出用户访问的网站域名.现已被部分国家用于互联网审查,如防火长城和韩国的KCSC(广播通信审议委员会).有两种方法可以解决这个问题.
Encrypted Client Hello
TLDR:在客户端和服务端交换公钥之前,使用客户端(浏览器)提前知道的公钥加密数据.
经典依旧 : In August 2020, the Great Firewall of China started blocking ESNI traffic, while still allowing ECH traffic.