Back
Featured image of post 计算机网络学习

计算机网络学习

概述

OSI 参考模型

网络通信是一个庞大的工程,其中网络协议就采用分层设计的思想。国际化标准组织提出了 开放式系统互联模型(open system interconnection model),简称 OSI 模型。

物理层(physical layer),负责管理通信设备和通信媒介,对设备的针脚、电压、线缆等做了详尽的定义。物理层为我们屏蔽了物理设备的底层细节,为上层提供信息比特传输能力。至于它对于线缆、电压等的规定,软件研发人员一般不需要关注。

数据链路层(data link layer)以 帧(frame)为传输单元,负责网络寻址、错误侦测,可以解决同一网络内多台主机的通信问题,最终实现局域网通信。

网络层(network layer)以 数据包(packet)为传输单元,负责路径选择和数据包转发。网络层建立在数据链路层的基础上,它实现了全网通信能力,可以将数据传送到网络中任一的节点。数据链路层只实现了局域网内的通信能力,为网络层提供服务,将包送至下一节点。两者分工明确,彼此协作。

传输层(transport layer)在网络层基础上,为应用进程提供端到端的通信服务,支持面向连接的数据流、流量控制以及可靠性保障。一个节点上通常有许多应用进程,为此传输层引入了端口的概念,实现了从进程到进程的传输能力。在 段(segment)中包含端口信息,网络转发后目标节点在包中取出段,再根据端口号送至目标进程。

应用层(application layer)负责应用的通信逻辑,并提供多样的网络应用服务。

  • 会话层(session layer)为通信实体实现会话和连接管理功能,主要提供 用户认证、权限控制 等服务。
  • 表示层(presentation layer)为不同终端的用户提供一致的数据表示和变换方法,比如 数据的编解码、数据的加密压缩、数据的压缩解压 等等。

TCP/IP 协议栈

网络访问层(network access layer)负责管理物理介质,并提供将数据从当前节点传输到下一节点的能力,相当于 物理层 + 数据链路层。

  • 不同的通信介质,有不同的接入设备,采用的协议也不同:Ethernet,以太网协议;PPP,点对点协议;DSL,用户数字线路 等等。

网络互连层(internet layer)在网络访问层提供的局域网通信能力之上,实现网际通信能力,负责路径选择和数据包转发,相当于 网络层。

  • IP 协议在该层以 IP 包为通信单元,通过该协议通信的主机需要分配一个 IP 地址,包中用 IP 地址来标识包的来源和目的地。

传输层(transport layer)在网络互连层点到点传输能力基础上,实现端到端的进程间通信。

  • UDP 协议引入了一个端口号,当 UDP 段搭载在 IP 包中送给主机后,系统根据段中的数据提交给对应的进程。
  • TCP 协议相对 UDP协议 来说,它为进程提供可靠、有序的数据流。

应用层(application layer)定义具体网络应用的通信逻辑,让应用进程间的写作成为可能。

  • 网络应用协议常见的有:HTTP、HTTPS、FTP、SMTP 等等。

Ethernet:以太网协议

物理层+数据链路层

物理层的任务主要是确定与传输媒体接口的一些特性:

  • (1)机械特性:指明接口接线器的形状和尺寸、引脚数目和排列、固定和锁定壮志等。
  • (2)电气特性:指明接口电缆的各线上出现的电压的范围。
  • (3)功能特性:指明某条线上出现的某一电平的电压的意义。
  • (4)过程特性:指明对于不同功能的各种可能事件的出现顺序。

对于数据链路,我们先知道链路是指一段物理线路,中间没有任何交换节点;数据链路则是说,我们在这条物理线路上还需要一些通信协议来控制数据的传输。

一般通过网络适配器来实现这些协议,它通常包括了数据链路和物理层的功能。

以太网帧结构

在以太网中,数据通信达基本单位是 以太网帧(frame),由 头部(header)、数据(data)以及 校验和(checksum)三部分构成。

以太网帧头部包含 3 个字段:

  • 目的地址,长度为 6 个字节,用于标记数据由哪台机器接收。
  • 源地址,长度为 6 个字节,用于标记数据由哪台机器发送。
  • 类型,长度为 2 字节,用于标记数据如何处理,0x0800 表示该帧数据是一个 IP 包。

以太网数据是任何需要发送的信息,长度可变,46 至 1500 字节均可。

以太网校验和,长度为 4 个字节。因为物理信号可能受到环境干扰,网络设备传输的比特流会出错,为了保证传输以太网帧的时候是完好无损的,我们就要用到校验和。

  • 发送者负责为每个以太网帧计算校验和,并计算结果填写在校验和字段中;接收者接收到以太网帧后,重新计算校验和并与校验和字段对比;如果两个校验和不一致,说明该帧在传输时出错了。

MAC 地址

MAC(Media Access Control Address)地址也是以太网地址,也叫硬件地址、物理地址、网卡地址。

  • 物理层上,网卡负责比特流和电信号之间相互转换。
  • 软件层上,内核协议栈负责封装以太网帧,并调用网卡驱动发送;接收数据时,负责验证 目的地址、校验和 并取出数据部分交予上层协议栈处理。

MAC 地址由 6 个字节(48位)组成。

  • 3 字节长的 厂商代码(OUI),由国际组织分配给不同的网络设备商。
  • 3 字节长的 序列号(SN),由厂商分配给它生产的网络设备。

MAC 地址很难直接用 ASCII 码来解读,所以我们将一个 8 位 字节的字符,分成 高 4 位 和 低 4 位,并且每个部分用 16 进制的字符来表示。

在 Linux 系统中,我们用 ip link 命令来查看网卡。

[root@iZ7xvfomazz4187zib4aurZ ~]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 00:16:3e:03:2c:8d brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
    link/ether 02:42:61:8f:61:b8 brd ff:ff:ff:ff:ff:ff

我们也可以通过套接字编程直接向系统获取,Linux 套接字支持通过 ioctl 系统调用获取网络设备,大致步骤如下:(补充内容请看附录)

​ 1、创建套接字。

​ 2、准备 ifreq 结构体,保存网卡设备信息。

​ 3、将查询网卡名填充到 ifreq。

​ 4、调用 ioctl 系统,向套接字发送 SIOCGIFHWADDR请求获取物理地址。

# include <net/if.h>
# include <stdio.h>
# include <string.h>
# include <sys/ioctl.h>
# include <sys/socket.h>

void mac_ntoa(unsigned char *n, char *a){
    // 格式化数据,并写入字符串 a
    sprintf(a, "%02x:%02x:%02x:%02x:%02x:%02x", n[0], n[1], n[2], n[3], n[4], n[5]);
}

int main(int argc, char *argv[]){
    if (argc < 2){
        fprintf(stderr, "no iface given\n");
        return 1;
    }
    
    // 创建套接字
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == s){
        perror("Fail to create socket");
        return 2;
    }
    
    // 填充字符串名字
    struct ifreq ifr;
    strncpy(ifr.ifr_name, argv[1], 15);
    
  	// 调用 ioctl 驱动获得地址
    int ret = ioctl(s, SIOCGIFHWADDR, &ifr);
    if (-1 == ret){
        perror("Fail to get mac address");
        return 3;
    }
    
    // 转换为可读形式
    char mac[18];
    mac_ntoa((unsigned char *)ifr.ifr_hwaddr.sa_data, mac);
    
    
    // 输出结果
    printf("IFace: %s\n", ifr.ifr_name);
    printf("MAC: %s\n", mac);

    return 0;       
}

MTU 最大传输单元

如果待发送的数据超过帧的最大承载能力,就需要先对数据进行分片,然后再通过若干个帧进行传输。

待发送的数据总共 4000 字节,假设以太网设备一帧最多只能承载 1500 字节。很明显,数据需要划分成 3 片,再通过 3 个帧进行发送。

编程发送以太网帧

实现 sendether 命令,用于发送以太网帧,它的用法是:

  • -i -iface:用于发送以太网帧的网卡名。
  • -t -to:目的 MAC 地址。
  • -T -type:以太类型。
  • -d -data:待发送数据。
send_ether -i enp0s8 -t 0a:00:27:00:00:00 -T 0x1024 -d "Hello, world!"

使用 enp0s8 网卡,向 0a:00:27:00:00:00 发送一个类型为 0x1024 的以太网帧。

IP:互联网协议

网络层

在数据链路层中,我们能够实现同一网络内多台主机之间的通信问题,网络适配器将数据封装为以太网帧然后发送给其他主机。

为了能够实现更大规模的网络互联,我们在数据链路层的基础上添加了网络层。

网络层做了什么呢?

它给参与网络层通信的主机都分配一个唯一地址,并且这些地址是按照网络拓扑结构进行分配的,保证一个组织内部的地址是连续的。

然后,它将多个不同的以太网用转发设备进行互联,这个转发设备维护路由表,规定了目的地址和下一条都对应关系。

IP 包结构

IP 是 互联网协议 ( internet protocol ) 的简称,是 TCP/IP 协议栈中的网络层协议。IP 协议在发展的过程中,衍生出 IPv4 和 IPv6 两个不同版本。其中,历史版本 IPv4 目前仍广泛使用;后继版本 IPv6 世界各地正在积极部署。

IP 地址详解

  • 主机号比特全为 0 ,是网络的起始地址,用于表示网络本身,一般称为 网络地址 ;
  • 主机号比特全为 1 ,是网络的结束地址,用于向网络内的所有主机进行广播,一般称为 广播地址 ;

子网掩码

我们可以通过子网长度来划定子网个数,一般使用掩码来记录 IP 地址中的网络号部分。

掩码位数与 IP 地址一样,1 表示该位属于网络号,0 表示该位属于主机号。如果你学过 C 语言,应该知道通过按位与操作 & ,掩码可以快速取出一个 IP 地址的网络号。

这就是所谓的 子网掩码 ,它也可以用 点分十进制表示法 来表示,用来描述 IP 地址的网络号部分。因此,子网 10.0.0.x 可以表示成 10.0.0.0/255.255.255.0 ;A 类网络 10.x.x.x可以表示成 10.0.0.0/255.0.0.0 。

实际上,描述网络号还有更简洁的方法:在 IP 地址后面加上斜杆和网络号的位数。例如:

  • 10.0.0.0/255.255.255.0 可以表示成 10.0.0.0/24 ;
  • 10.0.0.0/255.0.0.0 可以表示成 10.0.0.0/8 ;

TTL,IP 包存活时间

环 ( cycle ),一种首尾相连的特殊路径,是图论中重点研究的对象。如果 IP 包陷入环路的包无法清理,最终将耗尽线路的带宽和路由器的处理资源,影响正常数据的传输,造成大量丢包。

IP 包中的 TTL 字段是 Time To Live 的缩写,表示 IP 包的存活时间,如果 IP 包存活时间超过 TTL,路由就会将它丢弃。这样就算 IP 包陷入循环,经过一小段时间,它也会从网络中消失,不会造成更大的影响。

但因为网络路由器数量巨大,很难做到时间同步,因此,在实际工程实现中,TTL 并不是直接保存存活时间,而是保存 IP 包失效前可以经过的路由跳数。IP 包每经过一跳路由,TTL 都会减一;当 TTL 减到 0 ,路由便将它丢弃。

[root@yikuanzz ~]# cat /proc/sys/net/ipv4/ip_default_ttl
64

网络规划

假设,公司现在要给每个部分组建一个局域网,每个局域网通过一台路由器接到核心路由网络。

我们还注意到,不同的部门,主机数量也有多有少:

部门 主机数量
行政部 50
研发部 100
市场部 80
人力部 50
法务部 50

因此,每个部门局域网所需要的网段,大小也是不一样的。假设管理员拿到了一个地址段 192.168.170.0/23 ,如何将地址划分给图中的子网呢?

按照上述划分,我们就可以将各个部门的子网都分配好了。

ARP:地址解析协议

ARP 协议原理

我们知道,可以通过 IP 地址来指定数据包发送的目标地址,但是在数据链路层封装为帧的时候,怎么知道目标 IP 的 MAC 地址呢?

最直觉的方法就是让主机发送广播,询问网络中的其他主机来去得到目标 IP 的 MAC 地址。

ff:ff:ff:ff:ff:ff 是一个特殊的广播地址,目的地址为 ff:ff:ff:ff:ff:ff 的以太网帧,将被送到以太网中的每一台主机。交换机收到目的地址为 ff:ff:ff:ff:ff:ff 的帧后,也会将它广播到所有端口。

当主机收到目标 IP 发给自己的 MAC 地之后将其放在映射表中,后面传输的时候就可以直接从缓存中查询了。当然,为了保证时效性,ARP 缓存是要设置有效期到,如果缓存失效了,系统就必须重新发起 ARP 查询并且缓存最新结果。

那么,ARP 协议就是对这个过程进行统一的规范,并且 ARP 报文是内嵌在以太网帧之中的。

一个 ARP 报文中有 9 个字段,分别是:

  • 硬件类型( hardware type ),数据链路层协议类型,例如: 1 表示以太网协议;
  • 协议类型( protocol type ),网络层协议类型,例如 0x0800 表示 IP 协议;
  • 硬件地址长度( hardware address length ),数据链路层地址长度,对以太网来说,地址长度为 6 字节;
  • 协议地址长度( protocol addess length ),网络层地址长度,对 IP 协议来说,地址长度为 4 字节;
  • 操作码( operation ),报文的操作类型,1 表示 请求 ( request ),2 表示 应答 ( reply );
  • 源硬件地址(sender hardware address),即发送者的数据链路层地址;
    • 对于 ARP 请求,该字段表示请求发起者的地址;
    • 对于 ARP 应答,该字段表示请求应答者的地址,也就是发起者查找的地址;
  • 源协议地址( sender protocol address ),即发送者的网络层地址;
  • 目标硬件地址(target hardware address),即接收者的数据链路层地址;
    • 对于 ARP 请求,这个字段被忽略;
    • 对于 ARP 应答,这个字段表示请求发起者的地址;
  • 目标协议地址( target protocol address),即接收者的网络层地址;
    • 对于 ARP 请求,这个字段就是待查找地址;
    • 对于 ARP 应答,这个字段表示请求发起者的地址;

拓展阅读:RFC828

ARP 攻击

因为 ARP 协议没有真伪校验机制,如果黑客进入到主机所在的网络,他就可以轻易地监听主机发送的网络信息。

在 ARP 广播的时候,黑客不断地发送伪造的 ARP 回应给主机, 当然黑客为了保持隐蔽,它还是会将包转发给网关。这样,主机以为自己将包发送给了网关,而网关也误以为自己收到了主机发送的数据。

那么,怎么防止 ARP 攻击呢?

  • 双绑措施

    在路由和终端上同时进行 IP 地址和 MAC 地址的绑定,相当于人工管理 ARP 缓存,自行维护 IP 地址和 MAC 地址的对应关系。既然将 ARP 协议弃之不用,黑客就无法发起攻击了。

    这个方案虽然可以奏效,但维护非常繁琐。换个网卡或 IP 地址,都需要重新修改配置。当流动电脑临时接入时,也要即时进行绑定,费时费力。

  • 交换机端口绑定

    跟双绑措施类似,只不过将 IP 和交换机端口进行绑定,缺点也是类似的。

  • PPPoE

    使用 PPPoE 协议对网络流量进行二次封装,为每个用户都分配账号密码,上网时必须通过认证。这样 ARP 报文在一个认证的通道中传输,也就不会遭受攻击了。

    但 PPPoE 也不是完美的,由于二次封装的存在,传输效率会打些折扣。更严重的是,PPPoE 方式下局域网内无法互访。如果局域网内需要部署文件服务器、打印机,就有麻烦了。

其实最关键的一点在于,坚持使用 HTTPS 这样的加密协议。这样就算遭遇 ARP 劫持,信息也不会被窃取。在陌生的网络环境中,应该尽量不用 HTTP 这样的明文协议。

ICMP:互联网控制报文协议

ICMP 协议概述

互联网控制消息协议 ( Internet Control Message Protocol )简称 ICMP ,是 IP 的辅助协议,同样位于网络层,负责网际通信中 控制信息 和 差错信息 的传送。

ICMP 有赖 IP 提供的网际通信能力,它的报文作为数据承载在 IP 包中进行传输。

ICMP 报文同样分为 头部 和 数据 两部分,其中头部字段如下:

  • 类型 ( type ),顾名思义用来标识 ICMP 报文的类型,例如 目的不可达 ( destination unreachable )差错报文;
  • 代码 ( code ),进一步划分 ICMP 报文的类型,标识错误的原因,例如目的不可达可以进一步分成网络不可达、主机不可达以及端口不可达等等;
  • 校验和 ( checksum ),用于差错校验,由 ICMP 头部计算得出;

拓展阅读:RFC 792

UDP:用户数据报协议

传输层概述

在网络层中,我们实现点对点之间的数据传输,就是将数据封装为 IP 包,在包头指定目标主机的 IP 地址,然后在数据链路层封装为以太网帧,发送给路由器,再由路由器进行分发。

当然,一台主机上不可能指挥允许一个进程,点到点之间的数据传输并不能满足我们的需求,也就是说,我们需要进程与进程之间的传输。

于是在上述基础上,我们添加了传输层,引入了端口(port)的概念,用来区分不同的通信端点。

一台主机上可以有很多个通信端口,应用进程可以关联到一个或多个端口。当进程需要发送数据时,它必须申请一个端口,数据从该端口发送出去;当某个端口有数据到达时,操作系统负责将数据提交给对应的应用进程。

这种从进程到进程的传输能力,可以叫做端到端的传输。

我们有一些知名的端口号:

服务 端口
FTP文件传输 20 、 21
SSH安全远程登录 22
SMTP邮件传输 25
DNS域名系统 53
Web 80 、 443

UDP 数据报格式

UDP 是 用户数据包协议 ( user datagram protocol )的简称,它是一种简单的数据报式传输层协议。UDP 数据报结构非常简单,头部只包含端口号等若干个字段。

由于 UDP 位于传输层,因而报文有时也称为 UDP 段或 UDP 分组。UDP 报文也分为头部和数据两个部分,结构跟我们在上节讨论的传输层段几乎一模一样。其中,头部只有 4 个字段:

  • 源端口( source port ),发送方的端口号;
  • 目的端口( destination port ),接收方的端口号;
  • 报文长度( length ),即整个 UDP 报文的长度,包括头部和数据,单位为 字节 。
  • 检验和( checksum ),与 IP 校验不同,UDP 整个报文都会参与校验和计算,除此之外,UDP 还会在报文前面拼接一个 IP 伪头部,同时参与校验和计算;

UDP 报文需要借助 IP 协议提供的主机通信能力,作为数据搭载在 IP 包中发往目标主机。

拓展阅读:RFC 768

TCP:传输控制协议

TCP 协议简介

UDP 数据报是借助 IP 包提供的点对点传输能力来实现的端到端的传输,然而 IP 协议只是一种“尽力而为”的网络协议,它无法保证 IP 包一定能够送到目标主机。实际上,由于网路链路拥堵或者中间路由设备故障点存在,会有 IP 丢包的现象出现。此外,UDP 协议也没有流量控制机制、也没有拥塞控制机制,当遇到某些情况时是无法解决的。

为了解决 UDP 协议的局限性,一种更高级的传输协议被设计出来了 TCP(Transimission Control Protocol)它是一种 面向连接的流式协议 ,可为应用程序提供 可靠的字节流传输服务 。

  1. 连接主动发起方(一般是客户端),向被动连接方(一般是服务端)发出 SYN ;
  2. 被动连接方收到 SYN 后,向主动发起方回复 SYN+ACK ;
  3. 主动发起方收到 SYN 后,向被动连接方回复 ACK ;

其中,SYN 指令表示序号同步请求,ACK 表示确认,即对同步请求进行确认。

TCP 将数据组织成连续的字节流,每个字节均可由一个唯一的序号来标识。

TCP 在发送数据时,会将数据的 起始序号 和 长度 告诉对端,接收方收到数据后,将发送 ACK 对数据进行确认。ACK 中包含确认序号,它的值为最后一个已接收字节的序号加一,也就是接收方期望收到的新数据的起始序号。

那么,TCP 如何实现 流量控制 和 拥塞控制 的呢?

  • 流量控制。根据 TCP 协议规定,接收方需要维护一个 接收窗口 ,我们可以将它看作内存中的一个缓冲区。在连接建立和数据传输的过程中,接收方会将自己的接收窗口大小通告给发送方。发送方必须保证,发送的数据不超过接收窗口。如果接收窗口被占满,发送方就暂停发送新数据。

  • 拥塞控制。TCP 发送方自己在内部维护了一个 发送窗口 ,也叫做 拥塞窗口 。这是一个为发送策略算法服务的虚拟概念,表示可以发送的字节数(包含已经发送但仍未确认部分)。发送窗口一开始呈指数增长,比如每收到一个 ACK 就将它翻倍;当接收窗口增大到一定水平,增长速度降为线性增长。这就是 TCP 的 慢启动 过程,在网络状况良好的前提下,不断提高发送速度。如果网络发生拥塞,有数据丢包,这时 TCP 必须重传数据。发送方在重传数据的同时,还会降低发送窗口大小。这种情况下,大部分 TCP 实现会将发送窗口降为原来的一半,以便快速响应,避免进一步堵塞网络。这个机制也被称为 指数退避 。

最后是 TCP 的关闭,假设主机①数据发送完毕,它向主机②发出 FIN ,成为主动关闭方;主机②则成为被动关闭方,它回复 ACK 后,从主机①到主机②的数据流关闭。这时,连接处于 半关闭状态 ,反方向的数据流仍然有效。也就是说,这时主机②还可以向主机①发送数据。当主机②也发完数据,它同样发出 FIN 指令,告诉主机①数据流关闭;主机①收到 FIN 并回复 ACK 后,连接就完全关闭了。这就是 TCP 连接关闭的主要步骤,也被形象地称为“四次挥手”。

TCP 报文段格式

由于 TCP 协议位于传输层,它的传输单元一般叫做 TCP段( segment ),也可译为 TCP分组 。当然了,也有不少文献将它笼统地称为 TCP报文 。

三次握手

  • MSS 选项。最大分组长度(maximum segment size)就表示一个 TCP 分组最多能够承载的数据量。
  • 窗口扩大因子。如果窗口字段表示的范围太小了,我们可用窗口字段大小与穿过扩大因子相乘,该相乘是指将数据左移。

拓展阅读:RFC793

四次挥手

一个 TCP 连接包含两个方向的传输通道,因此需要两对 FIN/ACK 分组,各自负责关闭对应的方向。因此,这两对 FIN/ACK 交互也被形象地称为 四次挥手 。

TCP 建立连接需要三次握手,关闭连接需要四次挥手,步骤相对繁琐。这意味着一个 TCP 连接应该有很多中间状态。

  1. 客户端发出 SYN 分组,连接进入 SYN_SENT 状态;
  2. 服务端收到客户端发来的 SYN 分组,它回复 SYN/ACK 分组,连接进入 SYN_RECV 状态;
  3. 客户端收到服务器的 SYN/ACK 分组,它回复 ACK 分组,连接进入 ESTABLISHED 状态;
  4. 服务器收到客户端的 ACK 分组,服务端连接也进入 ESTABLISHED 状态;
  5. 当连接处于 ESTABLISHED 状态时,客户端和服务端可以互相传输数据;
  6. 时序图中间的数据分组及其后的 ACK 分组,为实验中 SSH 服务向客户机返回自己的版本信息(这部分数据被 telnet 命令直接输出到屏幕中);
  7. 客户端准备退出时,它通过 FIN 分组通知服务端,连接进入 FIN_WAIT1 状态;
  8. 服务器收到客户端发来的 FIN 分组,它回复 ACK 分组进行确认,连接进入 CLOSE_WAIT 状态;
  9. 客户端收到服务器发来的 ACK 分组,连接进入 FIN_WAIT2 状态;
  10. 这时连接处于半关闭状态,服务器仍可以向客户端发送数据;
  11. 服务器发完剩余数据后,向客户端发送 FIN 分组,通知客户端关闭连接,服务端连接便进入 LAST_ACK 状态;
  12. 客户端收到服务器发来的 FIN 分组,回复 ACK 分组进行确认,客户端连接进行 TIME_WAIT 状态;
  13. 服务端收到 ACK 分组后,连接彻底关闭;
  14. 由于最后一个 ACK 分组可能会丢,客户端必须在 TIME_WAIT 状态等待一段时间,以便对服务器重传的 FIN 分组进行确认;

其中,可以再看一下的是 TIME_WAIT 状态中的 2MSL时长,MSL 是最大分组寿命( maximum segment lifetime )的简称,即一个 TCP 分组被丢弃前能够在网络中存在的最长时间。

将 TIME_WAIT 状态维持 2MSL 时长是出于这样的考虑:

假设最后一个 ACK 分组刚好在存活时间耗尽前到达对端主机,这时已经过了 MSL 时间。对端收到 ACK 后,就会立即关闭连接,不可能再发送 FIN 分组。但如果对端在收到 ACK 前刚刚重传了 FIN 分组,就必须再经过 MSL 时间才能保证 FIN 分组从网络中消失。因此,连接必须维持 TIME_WAIT 状态 2MSL 时间后才能释放,否则就可能对潜在的新连接造成干扰。

  • 如果最后一个 ACK 分组可以到达对端,最多只需要等待 2MSL 时间即可保证网络中没有对端重传的 FIN 分组;
  • 如果最后一个 ACK 分组丢失了,对端在 MSL 内已经重传好多次了;
    • 如果重传的 FIN 分组有一个可以到达本端,TCP 回复 ACK 后会重置定时器在 TIME_WAIT 继续等待 2MSL 时长;
    • 如果重传的 FIN 分组都丢了,说明网络质量很差,再等下去也没有意义了;

主动关闭方一般是客户端,并发一般不高,因此 TIME_WAIT 状态基本不会造成任何影响。如果一个高并发服务(比如 Web 服务)存在大量短连接,则可能留下很多 TIME_WAIT 状态的连接。由于 TIME_WAIT 状态套接字无法立即回收,它们将占用大量的系统资源,对服务的性能造成严重影响。

滑动窗口,TCP的流量控制

  • TCP 协议规定:当接收方收到一个数据后,必须回复 ACK 给发送方。这样发送方就能得到反馈,如果数据发出去后很长时间都没有收到 ACK 确认,说明数据很有可能已经丢失了。TCP 每次发送数据后,都会启动一个定时器。如果定时器超时还没收到对方确认,TCP 就会重新发送数据。

  • 由于承载 TCP 报文段的 IP 包是独立路由的,可能走不同的网络路径,无法保证一定按照发送顺序送达目标主机。 TCP 协议需要向上提供连续字节流传输服务,如果报文段错序到达,TCP 必须根据序号重新排列数据。另一方面,数据到达后目标主机后,接收方应用程序可能忙于其他事情,无法及时处理。鉴于这两个点,TCP 接收方需要在内存中准备一个接收缓冲区,用于临时保存数据。

接收缓冲区大小是有限的,如果应用进程处理缓慢,发送方还拼命发送,最终肯定会压垮接收方。因此,当缓冲区有变化时,接收方应该通过 窗口大小 字段,将它的剩余大小告知发送方。

接收方通告的窗口大小通常称为 通告窗口( advertised window ),可缩写为 awnd 。它起到约束发送方发送速度的作用:

  • 如果接收方应用进程繁忙,迟迟未读取缓冲区里的数据,那么窗口大小将慢慢变小;
  • 当窗口大小降为零,发送方就停止发送新数据;

通过通告窗口,发送方可以实时感知接收方缓冲区的状态,然后根据缓冲区剩余空间动态调整发送速度,这就是 TCP 的 流量控制( flow control )机制。

从数据发送方来说,数据状态的变化就是用滑动窗口来实现的。

TCP 协议规定,接收方收到数据后,必须发送 ACK 进行确认,以此实现可靠传输。然而,就算是一个小小的 ACK 确认,也需要一个完整 TCP 分组报文来承载,开销很大!

众所周知,最小的 TCP 分组包含 20 字节的 TCP 头部和 20 字节的 IP 头部,总共 40 字节。试想发送一个 40 字节的 TCP 报文,仅仅只为了告诉发送方 4 字节的确认号,效率得有多低!有效信息才占 10% !

为了提高传输效率,TCP 实现了 延迟确认( delayed ACK )机制。延迟确认顾名思义就是收到数据不立马确认,而是等上一段时间,跟其他数据一起发送。

拥塞窗口,TCP的拥塞控制

为实现可靠传输,TCP 实现了接收确认机制:当数据发生丢包时,重传数据。当网络链路负载很重,甚至发生拥塞时,丢包就会很频繁。这时如果 TCP 还拼命地重传数据,将进一步压垮网络!

网络拥塞(network congestion)是说,每条网络线路都是有带宽的,如果网络流量超超过带宽,丢包就是不可避免的。

  • 维护拥塞窗口,限制数据发送量;
  • 根据网络当前状态,实时调节拥塞窗口:
    • 如果长时间未收到 ACK 而发生数据重传,说明网络可能拥塞,缩小拥塞窗口,降低发送速度;
    • 每收到一个有效 ACK 都增大拥塞窗口,提高发送速度,因为这通常意味着网络状态良好;

TCP 还维护了 拥塞窗口( congestion window ),根据链路的拥塞程度来约束发送速度。

拥塞窗口跟滑动窗口类似,同样规定了发送方此刻能够发送出去的字节数,只不过它通过评估网络链路的拥塞程度,并由一定的算法计算而来的。 $$ W = min(awnd, cwnd) $$ 其中,awnd 为通知窗口大小,cwnd 为拥塞窗口大小。

TCP 学习 cwnd 的算法可以总结为:慢启动、拥塞避免、快速重传和快速恢复。

收到重复的确认,说明发出去的数据发生丢包,意味着网络可能发生拥塞。TCP 同样会将 ssthresh 设为当前拥塞窗口 cwnd 的一半,并重新执行慢启动算法加以应对。

快速重传算法会受到捎带确认机制的制约。试想接收方刚好没有数据要发,因此 ACK 确认被延迟,乃至合并,发送方就不会检测到重复确认。

为了解决这个矛盾,TCP 规定接收方接到乱序数据后就立马发送 ACK 确认。

新版 TCP( Reno )选择跳过慢启动,直接进入拥塞避免阶段,这就是所谓的 快速恢复。

  • 将 ssthresh 设为当前窗口的一半;
  • 将当前窗口设为 ssthresh ;
  • 执行拥塞避免,窗口大小线性扩张;

实际上,在 TCP 检测到网络拥塞时,窗口和 ssthresh 都是立马减半,因而被称为 乘法减小 或者 指数退避 。每次降低一半,这种下降速度其实也是很快的。

DNS:域名系统

域名系统的概述

域名是 网域名称 ( domain name )的简称,它是一串以点号分隔的字符串,用于标识一台或一组计算机。域名可作为 IP 地址的别名,更便于记忆。

实际上,域名是一个分层次的命名空间,各种域名都隶属于根域 . 。位于第一层的域名称为 一级域名 或 顶级域名 ;第二层的域名称为 二级域名 ;以此类推。

以 www.fasionchan.com.为例,从右往左读依次是:

  • 根
  • 一级域名(顶级域名):com
  • 二级域名:fasionchan
  • 三级域名:www

域名注册后,所有人拥有域名的管理权:不仅可以修改域名关联的 IP ,还可以分配子域名。域名 fasionchan.com 被注册后,可以修改它关联的 IP ,还可以随意添加子域名 www.fasionchan.com 。

域名的第一级是 顶级域 ,包括

  • 通用顶级域 ,例如 .com 、 .net 和 .org 等;
  • 国家和地区顶级域 ,例如 .cn 、 .us 等;
通用顶级域 含义
.com 商业公司
.edu 教育机构
.net 互联网服务供应商
.org 非营利组织、国际机构等
国家和地区顶级域 含义
.cn 中国
.hk 中国香港
.mo 中国澳门
.tw 中国台湾
.jp 日本
.us 美国

域名系统 ( domain name system ,简称 DNS ),是互联网提供的一项名字服务。我们可以将 DNS 看作一个分布式数据库,它保存着域名和 IP 的映射关系。

有了这个对应关系,我们就可以通过 域名 ( domain name )来访问网络服务,不用再苦苦记忆 IP 地址。要知道域名 www.fasionchan.com 比 IP 地址 163.181.33.224 好记多了。

域名注册后,所有人可将域名关联的 IP 登记到域名系统。这是一个分布式数据库,以域名为键,以 IP 为值。域名系统提供一些服务器用户查询,这就是 DNS服务器( dns server )。

DNS服务器工作原理

全球域名的最高管理机构是 ICANN ( Internet Corporation for Assigned Names and Numbers ),它是一个总部位于美国加州的组织。ICANN 负责管理整个域名系统的运作,主要工作是规划 顶级域名 ( top level domain ,简写为 TLD )。

顶级域名是域名的第一级,可分为两种:

  • 通用顶级域 ,例如 .com 、 .net 、 .edu 、 .org 等等;
  • 国家地区顶级域 ,例如 .cn 、 .hk 、.jp 、 .us 等等;

理论上,查询任何域名都需要先查询 ICANN 的根域。因为只有根域才能知道:某个域由谁托管,服务器是哪些。事实上也确实如此,ICANN 维护着一张映射表,记录了每个顶级域名和对应的托管商。

根域名服务器 ( root name server )保存 DNS 的根区列表。

根域名服务器列表可在 root-servers.org 上查询。 Anycast 路由技术:分散在不同地理位置的多台服务器,可以使用相同的 IP 地址。当发送方向这个 IP 地址发送数据时,路由协议会自动选择一个最近的节点。

[root@yikuanzz ~]# dig . NS

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.15 <<>> . NS
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24903
;; flags: qr rd ra; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;.				IN	NS

;; ANSWER SECTION:
.			7	IN	NS	j.root-servers.net.
.			7	IN	NS	g.root-servers.net.
.			7	IN	NS	c.root-servers.net.
.			7	IN	NS	h.root-servers.net.
.			7	IN	NS	e.root-servers.net.
.			7	IN	NS	l.root-servers.net.
.			7	IN	NS	m.root-servers.net.
.			7	IN	NS	d.root-servers.net.
.			7	IN	NS	b.root-servers.net.
.			7	IN	NS	a.root-servers.net.
.			7	IN	NS	i.root-servers.net.
.			7	IN	NS	f.root-servers.net.
.			7	IN	NS	k.root-servers.net.

;; Query time: 0 msec
;; SERVER: 100.100.2.136#53(100.100.2.136)
;; WHEN: Wed Jun 05 14:48:29 CST 2024
;; MSG SIZE  rcvd: 433

当我们查询一个域名时,必须从根服务器开始,逐层查询,这就是所谓的 迭代查询 ( iterative query )。

当我们查询一个域名,例如 www.fasionchan.com 时:

  1. 先查根域名服务器;
    • 根域名服务器就 13 台,IP 大家都知道,极少改动;
    • 根域名服务器保存根区列表,列表包含顶级域名的托管商,以及相关服务器信息;
    • 根域名服务器根据根区列表,告诉我们 .com 顶级域应该找谁查询;
  2. 根据根服务器返回结果,继续查询负责 .com 解析的服务器,一般叫做顶级域名服务器;
    • 主域名 fasionchan.com 注册后,需要将负责该域名解析的服务器,登记在 .com 顶级域名服务器上;
    • .com 顶级域名服务器根据这个信息,告诉我们 fasionchan.com 这个域名应该找谁查询;
  3. 根据顶级域名服务器返回结果,继续查询负责fasionchan.com解析的服务器,一般叫做权威域名服务器;
    • fasionchan.com 子域信息一般都登记在权威服务器上;
    • 权威服务器取出 www.fasionchan.com 对应记录,并返回给我们,查询结束;
    • 如果某个子域由其他权威服务器负责,我们还需要继续迭代,直到查询完毕;

一般来说,本地主机会通过本地的 递归解析器 去对网址进行递归,递归解析对客户端来说是完全透明的,客户端完全不用关心递归解析器背后的其他 DNS 服务器。

此外,递归解析器还会将查询结果在本地缓存起来。当域名再次被查询时,它可直接返回缓存结果,无须重新查询其他 DNS 服务器。正因如此,递归解析器通常被称为 DNS缓存服务器 。

DNS 报文格式

DNS 是一个典型的 Client-Server 应用,客户端发起域名查询请求,服务端对请求进行应答。

DNS 一般采用 UDP 作为传输层协议(TCP也行),端口号是 53。请求报文和应答报文均作为数据,搭载在 UDP 数据报中进行传输。

DNS 报文分为 请求 和 应答 两种,结构是类似的,大致分为五部分:

  • 头部( header ),描述报文类型,以及其下 4 个小节的情况;
  • 问题节( question ),保存查询问题;
  • 答案节( answer ),保存问题答案,也就是查询结果;
  • 授权信息节( authority ),保存授权信息;
  • 附加信息节( additional ),保存附加信息;

  • QR 位标记报文是一个查询请求,还是查询应答;
    • 0 表示查询请求;
    • 1 表示查询应答;
  • 操作码(opcode)占 4 位,表示操作类型;
    • 0 是标准查询;
    • 1 是反向查询;
    • 2 是服务器状态请求;
  • AA 表示 权威回答(authoritative answer),意味当前查询结果由域名的权威服务器给出;
  • TC 表示 截短(truncated),使用 UDP 时,如果应答超过 512 字节,只返回前 512 个字节;
  • RD 表示 期望递归(recursion desired),在请求中设置,并在应答中返回;
    • 该位为 1 时,服务器必须处理这个请求:如果服务器没有授权回答,它必须替客户端请求其他 DNS 服务器,这也是所谓的 递归查询 ;
    • 该位为 0 时,如果服务器没有授权回答,它就返回一个能够处理该查询的服务器列表给客户端,由客户端自己进行 迭代查询 ;
  • RA 表示 可递归(recursion available),如果服务器支持递归查询,就会在应答中设置告知客户端;
  • 保留位,未来扩展。
  • 响应码(response code)表示请求结果;
    • 0 表示没有差错;
    • 3 表示名字差错,该差错由权威服务器返回,表示待查询的域名不存在;

问题节支持保存多条问题记录,记录条数则保存在 DNS 头部中的问题记录数字段。这意味着,DNS 协议单个请求能够同时查询多个域名,虽然通常只查询一个。

  • 待查域名(Name),字段长不固定。
  • 查询类型(Type),除了关联 IP 地址,还可以关联其他信息常见类型包括:
    • 1 表示 A 记录,即 IP 地址;
    • 28 表示 AAAA 记录,即 IPv6 地址;
  • 类(Class)通常为 1,表示 TCP/IP 互联网地址;

服务端处理查询请求后,需要向客户端发送应答报文;域名查询结果作为资源记录,保存在答案以及其后两节中.

  • 有效期( TTL ),域名记录一般不会频繁改动,所以在有效期内可以将结果缓存起来,降低请求频率;
  • 数据长度( Resource Data Length ),即查询结果的长度;
  • 数据( Resource Data ),即查询结果;

1、请求报文实例。

2、应答报文实例

附录:补充知识点

Linux 内核 - ioctl

ioctl是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。

# include <sys/ioctl.h>

int ioctl(int fd, int cmd, ...)
Built with Hugo
Theme Stack designed by Jimmy
© Licensed Under CC BY-NC-SA 4.0