概念

协议族

一系列相关的协议的集合

体系结构或参考模型

指定一个协议族中的各种协议之间的相互关系并划分需要完成的任务的设计

面向连接

对于虚电路抽象和面向连接的分组网络(例如 X.25 ), 需要在每个交换机中为每个连接存储一些信息或状态. 原因是每个分组只携带少量的额外信息, 以提供到某个状态表的索引. 例如, 在 X.25 中, 12 位的 逻辑信道标识符(LCI)或逻辑信道号(LCN) 被用于这个目的. 每台交换机中, LCI 或 LCN 和交换机中的每个流状态相结合, 以决定分组交换路径中的下一台交换机.

在使用信令协议在一条虚电路上交换数据之前, 每个流状态已经建立, 该协议支持连接建立, 清除和状态信息. 因此, 这种网络称为 面向连接 的.

无连接

数据报起源于 CYCLADES 系统, 它是一个特定类型的分组, 有关来源和最终目的地的所有识别信息都位于分组中(而不是分组交换机中). 虽然这种通信通常需要较大的数据包, 但不需要在交换机中维护连接状态, 它可用于建立一个无连接的网络, 并且没必要使用复杂的信令协议.

消息边界或记录标记

当一个应用将多个信息块发送到网络中, 这些信息块可能被通信协议保留, 也可能不被通信协议保留.

  • 大多数数据报协议保存消息边界.
  • 在电路交换或虚电路网络中, 一个应用程序可能需要发送几块数据, 接收程序将所有数据作为一个块或多个块来读取. 这类型的协议不保留消息边界. 在底层协议不保留消息边界, 而应用程序需要它的情况下, 应用程序必须自己来提供这个功能.

ISO 网络七层

编号 名称 描述
7 应用层 软件
6 表示层 应用的数据表示格式及转换规则
5 会话层 多个连接组成一个通信会话的方法
4 传输层 相同计算机中的多个程序之间的连接或关联的方法
3 网络层 经过不同类型链路层网络的多跳通信方法
2 链路层 经过单一链路通信的方法
1 物理层 指定连接器, 数据速率和如何在某些介质上进行位编码

ARPANET 参考模型

这是最终 TCP/IP 协议族采纳的模型

编号 名称 描述
7 应用层 各种网络应用
4 传输层 提供在抽象的, 由应用管理的 “端口” 之间的数据交换. 如 TCP, UDP
3.5 网络层 协助完成网络层设置, 管理和安全的非正式的 “层”. 如 ICMP, IGMP, IPsec
2.5 链路层 用于网络层到基于多接入链路层网络的链路层的地址映射的非正式的 “层”. 如 ARP.

数据分解

DNS 服务器 / Web 服务器
ICMP / UDP / TCP / IGMP / SCTP / DCCP
ARP / IPv4 / IPv6
Ethernet

数据封装

           +---------------------------------+
           |                                 |
           |         N'th level PDU          |
           |    (Protocol Data Unit)         |
           |                                 |
+-----------------------------------------------------------+
|          |                                  |             |
|   N-1    |     N Level PDU                  |  N-1        |
|  Level   |                                  |  Level      |
|  Header  |                                  |  Tailer     |
+----------+----------------------------------+-------------+

如果某层获得由它的上层提供的 PDU, 它通常”承诺”不查看 PDU 中的具体内容. 这是封装的本质, 每层都将来自上层的数据看成不透明, 无须解释的信息.

最常见的处理是某层在获得的 PDU 前面增加自己的头部, 有些协议是增加尾部(不是 TCP/IP ).

头部用于在发送时复用数据, 接收方基本一个分解(拆分)标识符执行分解. 在 TCP/IP 网络中, 这类标识符通常是硬件地址, IP 地址和端口号.

头部中也包含一些重要的状态信息, 例如一条虚电路是正在建立还是已经建立.

TCP 发送到 IP 的 PDU 称为 Segment

端口

它是 16 位非负整数(0~65535). 在物理上没有指任何东西. 每个 IP 地址有 65536 个可用的端口号.

它只是作为应用程序与对方通信的一种方式.

标准的端口号由 IANA 分配.

  • 熟知端口号 (0~1023)
  • 注册端口号(1024 ~ 49151)
  • 动态/私有端口号(49152~65535)

IP 地址

IPv4. 共 32 位. 采用 点分四组/点分十进制 表示法. 每个数字的范围是 [0, 255] .

IPv6. 共 128 位, 是 IPv4 长度的 4 倍. 采用 块/字段的四个十六进制 表示. 这些块/字段的数, 用冒号分隔.

IPv6 简化规则

  • 一个块中的前导 0 必须压缩
  • 全零的块可以省略, 并用符号 :: 代替 (只用在压缩最多零的地方)
  • IPv6 嵌入 IPv4 地址可使用混合符号形式, 紧接着 IPv4 部分的地址块的值为 ffff , 地址其余部分使用 点分四组格式 . 例如 ::ffff:10.0.0.1 可以表示 IPv4 地址 10.0.0.1 . 它称为 IPv4 映射的 IPv6 地址
  • IPv6 地址的低 32 位通常采用点分四组表示法. 因此 ::0102:f001 相当于地址 ::1.2.240.1 . 它称为 IPv4 兼容的 IPv6 地址 . 起初用于 IPv4 与 IPv6 之间的过渡计划, 但现在不需要.
  • IPv6 的地址在 http 中可这样子写: http://[IPv4 地址]:端口 .

分类寻址

IPv4

网络号 (二进制表示中, 从左边开始算算) 主机 高序位 用途 百分比 网络数 主机数
A 0~7 bit. 但必顶是 0开始 (7 位 free) 24 位 0 单播/特殊 12 128 16777216
B 0~15 bit. 但必须是 10 开头(14 位 free) 16 位 10 单播/特殊 14 16384 65536
C 0~23 bit, 110 开头. (21 位 free) 8 位 110 单播/特殊 18 2097152 256
D 0~31bit. 1110 开头. (28 位 free). 组广播地址 1110 组播 116 N/A N/A
E 0~31 bit. 1111 开头. (28 位 free). 保留 1111 保留 116 N/A N/A
  • A, B, C 用于为 Internet (单播地址) 中的接口分配地址, 以及其他一些特殊情况下使用.
  • 类由地址中的头几位来定义
    • 0 : A 类
    • 10 : B 类
    • 110 : C 类

子网寻址

它没有为地址增加长度. 它只是将 ABC 类的主机号, 进一步用于站内分配. 进一步划分为一个子网号和一个主机号.

子网掩码

由一些 1 后跟一些 0 构成. 只需要给出一些连续位的 1 的掩码来简化. 这种格式也称为 前缀长度.

例如

128.0.0.0 用前缀长度来表示是 /1

将 IP 地址 与 掩码进行位与运算的结果, 就得到前缀地址了.

注意, 子网掩码纯粹是站点内部的局部问题.

广播地址

子网掩码按位取反, 然后与 IP 地址进行 位或 操作的结果.

链路层

它的 PDU 称为 帧. 它通常支持可变的帧长度, 范围从几字节到几千字节. 这个范围的上限称为 最大传输单元(MTU) .

以太网标准由 DEC, Intel 和 Xerox 公司在 1980 首首次发布, 并在 1982 年加以修订. 目前帧的格式称为 DIX格式 或Ethernet II格式 . IEEE 在此基础上加以修改, 形成一种 CSMA/CD 网络, 称为 802.3

802 前缀的标准, 定义了局域网和城域网的工作过程. 更多的, 请参考 IEEE 802 标准.

  • 802.3 : 以太网

  • 802.11 (WLAN/WIFI)

帧格式

image-20190701172436632

https://zh.wikipedia.org/wiki/%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%B8%A7%E6%A0%BC%E5%BC%8F

  • 帧的源地址和目的地址, 也称为 MAC 地址, 链路层地址, 802 地址, 硬件地址或物理地址.
  • 目的地址允许寻址到多个站点(称为广播, 或组播)
  • 广播功能用于 ARP 协议, 组播功能用于 ICMPv6 协议, 以实现网络层地址和链路层地址之间的转换
  • 以太类型 : 用于确定头部后面的数据类型.
    • 0x0800 为 IPv4
    • 0x86DD 为 IPv6
    • 0x0806 为 ARP
    • 0x8100 表示一个 Q 标签帧
    • 如果 该值 >= 1536 , 则表示类型. <= 1500 , 则表示长度. 可以看 ETHERTYPES 定义的所有列表
  • 一个帧基本大小是 1518 字节, 最近的标准将该值扩大到 2000 字节
  • 负载 (Payload) 是上层的 PDU . 一般最大为 1500 字节. 代表以太网的 MTU.
  • 最小帧大小为 64 字节, 要求 Payload 长度最小为 48 字节. 当 Payload 较小时, 填充字节(0)被添加到有效 Payload尾部, 以确保达到最小长度.
  • 最大帧长度是 1518 字节(包括 4 字节 CRC 和 14 字节头部)
  • 最大帧效率 = 1500 / (12 帧间距 + 7 前导 + 1 帧开始符 + 14 头部 + 4 字节 CRC + 1500 MTU) 约为 97.53%
  • 不同速率的以太网帧间隔 IFG 为
    • 10 Mb/s 为 9.6 µs
    • 100 Mb/s 为 960 ns
    • 1000 Mb/s 为 96 ns
    • 10 000 Mb/s 为 9.6 ns
  • 一种提升效率的方式是, 在以太网中传输大量数据时, 尽量使帧尺寸更大一些.
  • Linux 中查看以太网卡的信息 sudo ethtool eth0
  • 局域网唤醒(WoL)
    • 在 Linux 中, 它们可以被以下几种帧触发
    • 任何物理层活动 (p)
    • 发往站的单播帧 (u)
    • 组播帧 (m)
    • 广播帧 (b)
    • ARP 帧 (a)
    • 魔术分组帧 (g)
    • 可以用 ethtool 来配置 : ethtool -s eth0 wol umgb
    • 生成魔术分组帧命令 : wol 网卡MAC地址

流量控制

通过发送 PAUSE 帧来实现. 它是通过将以太网帧格式中的 长度/类型 字段值设置为 0x8808, 以及使用 MAC 控制操作码 0x0001 来标识.

地址解析协议 ARP

  • ARP 仅用于 IPv4
  • IPv6 使用邻居发现协议, 被合并入 ICMPv6

它用于32 位的 IPv4 地址和以太网的 48 位 MAC 地址之间的映射.

ARP 只在位于同一 IP 子网的系统时, 才能工作.

它的功能是从逻辑 Internet 地址, 向对应物理硬件地址进行转换.

PPP 不使用 ARP

ARP 发现地址 : ARP 向所有主机发送一个称为 ARP 请求 的以太网帧. 这被称为 链路层广播 ARP 请求包含目的主机的 IPv4 地址, 并寻找以下问题的答案 : 如果你将 IPv4 地址 xxx 配置为自己的地址, 请向我回应你的 MAC 地址

帧格式 :

image-20190702153330608

  • 目的以太网地址 ff:ff:ff:ff:ff:ff 是广播地址, 在同一广播域中的所有以太网接口可接收这些帧
  • OP 类型
    • 1: ARP 请求
    • 2: ARP 应答
    • 3: RARP 请求
    • 4: RARP 应答
  • tcpdump 跟踪 ARP : sudo tcpdump -i en0 -n arp
  • 查看映射表命令 arp -a
  • 网卡厂商 OUI 列表 http://standards-oui.ieee.org/oui.txt 或查询 https://hwaddress.com/
  • 更详细的请查 man arp
  • 查看 IP, MAC, HOSTNAME 的工具 brew install arp-scan; 然后执行 arp-scan --localnet

缓存超时

一般完整的条目超时为 20 分钟

不完整的条目超时为 3 分钟

free ARP

一台主机发送 ARP 请求以寻找自己的地址

它的目标

  • 允许一台主机确定另一台主机是否配置相同的 IPv4 地址
  • 如果发送 Free ARP 的主机已改变硬件地址, 该帧导致任何接收广播并且其缓存中有该条目的其他主机, 将该条目中的旧硬件地址更新为与该帧一致.

IP 协议

https://tools.ietf.org/html/rfc791

TCP/IP 协议族中的核心协议. TCP, UDP, ICMP 和 IGMP 都通过 IP 数据报传输.

它提供了一种尽力而为, 无连接的数据报交付服务.

  • 尽力而为, 表示不保证 IP 数据报能成功到达目的地. 任何可靠性, 必须由上层提供. (例如 TCP). IPv4, IPv6 都使用这种模式.
  • 无连接, 表示 IP 不维护网络单元(即路由器)中数据报相关的任何链接状态信息, 每个数据报独立于其他数据报来处理.
    • 这意味着, IP 数据报可不按顺序交付

IPv4 Header 格式

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • version : 4 bit. IPv4 则值为 4

  • IHL : 4 bit. (Internet Header Length). 它表示 32 bit 的数量.

    • 最小的是 correct header. 为 5
    • 包括选项的大小
    • 所以, 最大为 15 * 32 / 8 = 60 byte
  • ToS: 8 bit

    • Bits 0-2: Precedence.
    • Bit 3: 0 = Normal Delay, 1 = Low Delay.
    • Bits 4: 0 = Normal Throughput, 1 = High Throughput.
    • Bits 5: 0 = Normal Relibility, 1 = High Relibility.
    • Bit 6-7: Reserved for Future Use.
           0     1     2     3     4     5     6     7
        +-----+-----+-----+-----+-----+-----+-----+-----+
        |                 |     |     |     |     |     |
        |   PRECEDENCE    |  D  |  T  |  R  |  0  |  0  |
        |                 |     |     |     |     |     |
        +-----+-----+-----+-----+-----+-----+-----+-----+
  Precedence
  • 111 - Network Control
  • 110 - Internetwork Control
  • 101 - CRITIC/ECP
  • 100 - Flash Override
  • 011 - Flash
  • 010 - Immediate
  • 001 - Priority
  • 000 - Routine

  • Total Length : 16 bit. 包括 header 和 data. 最大为 65535 byte.

    • Header 最大为 60 byte, 但典型的为 20 byte.
    • IPv6 的头部固定为 40 字节
    • 以太网会将短帧填充到最小长度 64 字节. 即范围为 [64, 65535]
    • 对于分片, 则这个长度是指分片的总长度, 而不是整个数据的长度.
    • IP 协议要求至少为 576 bytes(IPv4), 1280(IPv6) https://en.wikipedia.org/wiki/Maximum_transmission_unit
  • Identification : 16 bit

  • Flags : 3 bit.

    • bit 0 : 必须为 0
    • Bit 1 : (DF) 0 = May Fragment, 1 = Don’t Fragment.
    • Bit 2 : (MF) 0 = Last Fragment, 1 = More Fragments.
  • Fragment Offset: 13 bit

  • Time to Live: 8 bits

    • 如果值为 0, 则该数据被销毁
    • 单位为秒
    • 但实际意义为经过路由器数量的上限. RFC 1122 建议初始值为 64. 每经过一个路由器, 就会减 1.
  • Protocol : 8 bit . 协议类型. 值参考 https://tools.ietf.org/html/rfc790

  • Header Checksum: 16 bit

    • 只对 header 进行校验
  • Source Address : 32 bit

  • Destination Address : 32 bit

  • Options : 可变长度. (范围 0~40 字节)

  • Payload : 数据 data, 最多 65515 字节

IP Header 字节序

TCP/IP 头部中的所有二进制整数在网络中传输时所需的字节顺序是 高位优先(Big Endian),大端 . 也称为 网络字节序

但大多 PC 使用 低位优先(Little Endia), 小端

ICMPv4 和 ICMPv6

Internet 控制报文协议

UDP

它是一种保留消息边界的简单的面向数据报的传输层协议. 它不提供差错纠正, 队列管理, 重复消除, 流量控制和拥塞控制. 它提供差错检测, 包含我们在传输层碰到的第一个真实的端到端(end-to-end)校验和.

这种协议自身提供最小功能, 因此, 使用它的应用程序要做许多关于数据包如何发送和处理的控制工作.要想保证数据被可靠投递或正确排序, 应用程序必须自己实现这些保护功能.

UDP header

https://tools.ietf.org/html/rfc768

                  0      7 8     15 16    23 24    31
                 +--------+--------+--------+--------+
                 |     Source      |   Destination   |
                 |      Port       |      Port       |
                 +--------+--------+--------+--------+
                 |                 |                 |
                 |     Length      |    Checksum     |
                 +--------+--------+--------+--------+
                 |
                 |          data octets ...
                 +---------------- ...

                      User Datagram Header Format
  • source port, des port, length, checksum 各 2 字节.
  • UDP header 一共 8 字节
  • Length : 是 header 和 payload 的总长度. 以字节为单位. 最小值为 8(除非使用了带有 IPv6 越长数据报的 UDP)
  • 发送一个 0 字节数据(payload)的 UDP 数据报是允许的, 尽管很少见.

如果数据报不要求对方回复, 则 source port 可被设置为 0.

UDP 检验和

由发送方计算, 接收方校验. 它在传输中不会被修改(除非它通过一个 NAT). (IPv4 头部中的校验和只覆盖整个 header, 它在每个 IP 跳中都要被重新计算)

对于 UDP 来说, 校验和是可选的(尽管强烈推荐使用). 值得注意的是

  • UDP 数据报长度可以是奇数个字节, 而校验和算法只相加 16 位字(总是偶数个字节). UDP 的处理过程是在奇数长度的数据尾部追加一个值为 0 的填充(虚)字节. 这仅仅是为了校验和的计算和验证. 这个虚字节不会被传送出去的.
  • UDP(也包括 TCP) 计算它的校验和时包含了(仅仅)衍生自 IPv4 头部的字段的一个 12 字节的伪头部或衍生自 IPv6 头部字段的一个 40 字节的伪头部. 这个伪头部也是虚的, 它的目的只是用于检验和的计算, 也不会被传送出去. 这个伪头部包含了来自 IP 头部的源和目的地址以及协议或下一个头部字段(它的值应该是 17). 它的目的是让 UDP 层验证数据是否已经到达正确的目的地.

image-20190723145656866

TCP

滑动窗口 sliding window

  • 发送方
    • 记录着哪些分组可以被释放, 哪些分组正在等待 ACK, 以哪些分组还不能被发送
  • 接收方
    • 记录着哪些分组已经被接收和确认, 哪些分组是下一步期望的(和已经分配多少内存来保存它们), 以及哪些分组即使被接收也将会因内存限制而被丢失.

变量窗口

流量控制

两种方式

  • 基于速率. 给发送方指定某个速率, 同时确保数据永远不超过这个速率发送
  • 基于窗口. (window-based). 使用滑动窗口时最流行的方法. 允许窗口大小随时间而变动.

拥塞控制

涉及发送方减低速度以不至于压垮其与接收方之间的网络.

超时与重传

RTT : 往返时间估计.

两套独立的重传机制

  • 基于时间.
  • 基于确认信息的构成. 这种通常更高效.基于接收端的反馈信息来引发重传.

TCP 在发送数据时会设置一个计时器, 若至计时器超时仍未收到数据确认信息, 则会引发相应的超时或基于计时器的重传操作. 计时器超时, 称为 重传超时(RTO)

另一种称为 快速重传, 通常发生在没有延时的情况下. 若 TCP 累积确认无法返回新的 ACK, 或者当 ACK 包含的选择确认信息(SACK)表明出现失序报文段时, 快速重传会推断出现丢包.

TCP 拥有两个阈值来决定如何重传同一个报文段.

  • R1 表示 TCP 在向 IP 传递消极建议(如重新评估当前 IP 路径)前, 愿意尝试重传的次烽(或等待时间).
  • R2 (大于 R1)指示 TCP 应放弃当前连接的时机.

R1 和 R2 应分别至少设为 3 次重传和 100 秒.

对数据段, 分别对应系统配置变量 net.ipv4.tcp_retries1 (默认为 3), 以及 net.ipv4.tcp_retries2(默认为 15, 对应约 13 ~ 30 分钟, 根据具体连接的 RTO 而定) . 值为重传次数, 而不是时间.

对于 SYN 报文段, net.ipv4.tcp_syn_retriesnet.ipv4.tcp_synack_retries 限定重传次数. 默认为 5 , 约 180 秒.

快速重传算法 : TCP 发送端在观测到至少 dupthresh 个重复 ACK 后, 即重传可能丢失的数据分组, 而不必等到重传计时器超时. 当然也可以同时发送新的数据.根据重复 ACK 推断的丢包通常与网络拥塞有关, 因此伴随快速重传应触发拥塞控制机制. 不采用 SACK 时, 在接收 到有效 ACK 前至多只能重传一个报文段. 采用 SACK, ACK 可包含额外信息, 使得发送端在每个 RTT 时间内可以填补多个空缺.

设置 RTO

TCP 在收到数据后会返回确认信息, 因此可在该信息中携带一个字节的数据(采用一个特殊序列号)来测量传输该确认信息所需时间. 每个此类的测量结果称为 RTT 样本.

  • 经典方法. SRTT <- a(SRTT) + (1-a)RTTs . RTO = min(ubound, max(lbound, (SRTT)/b))
    • SRTT : 平滑的 RTT 估计值
    • a : 平滑因子. 推荐为 0.8 ~ 0.9
    • RTTs : 新的样本值
    • SRTT 现存值
    • 上式表明, 新的估计值有 80%~90%go 来自现存值. 10%~20%来自新测量值
    • b 为延时离散因子. 推荐值为 1.3 ~ 2.0
    • Ubound 为 RTO 的上边界(可设置建议值, 如 1 分钟)
    • Lbound 为 RTO 的下边界(可设置建议值, 如 1 秒或约 2 倍 SRTT)
  • 标准方法. 略~
    • TSopt 的 RTT 测量.
    • TCP 发送端在其发送的每个报文段的 TSopt 的 TSV 部分携带一个 32bit 的 ts 值. 它是发送时刻的 TCP 时钟值
    • 接收端记录接收到的 TSV 值(名为 TsRecent 变量)并在对应的 ACK 中返回, 并且记录上一个发送的 ACK 号(名为 LastACK). ACK 号代表接收端期望接收 的下一个有序序号.
    • 当一个新的报文段到达时, 其序列号与 LastACK 的值吻合, 则将其 TSV 值存入 TsRecent
    • 接收 端发送的任何一个 ACK 都包含 TSopt, TsRecent 变量包含的 ts 值被写入其 RSER 部分
    • 发送端接收到 ACK 后, 将当前 TCP 时钟送去 TSER 值, 得到的差即为新 RTT 样本估计值.
    • Linux 中的 net.ipv4.tcp_timestamps 系统配置变量控制是否使用该选项.(0 为禁用, 1 为启用)
    • Linux 中的 RTO = srtt + 4(rttvar)
    • 查看某个 IP 目的地址的统计信息: ip tcp_metrics show 202.108.43.218
    • 旧版的话是使用 ip route show cache 202.108.43.218
    • 注意. rttvar 最小值不小于 50 ms, 所以 RTO 不会小于 200ms.

服务模型

  • 面向连接
  • 可靠的字节流
  • 单播

可靠性

TCP 把应用程序发送的字节流, 转换成一组 IP 可以携带的分组. 这称为 组包(packetization), 这些分组包含序列号, 这些序列号代表了每个分组的第一个字节在整个数据流中的字节偏移, 而不是分组号.

这允许分组在传送中是可变大小的, 并允许它们组合, 称为 重新组包 (repacketization)

分组的大小, 一般使得每个报文段按照不会被分片的单个 IP 层数据报的大小划分.

TCP -> IP 的块, 称为 Segment, 报文段

一个指示字节号 N 的 ACK, 暗示着所有直到 N 的字节 (但不包含 N) 已经成功被接收了.

TCP 提供全双工服务.

TCP Header 和封装

|                           IP PDU                                                     |
| IP 头,ipv4 20 byte,无选项, ipv6 40 字节| TCP 头, 20 字节,无选项 | TCP Payload            |
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                            TCP Header Format

来源 : https://tools.ietf.org/html/rfc793

TCP 基本头部为 20 字节(不带选项). 通常 SYN (即初始建立连接时) 的为 44 字节.

TCP 唯一标识一条连接: Src IP + Src Port : Dest IP + Dest Port (四元组)

一个 IP 地址和一个端口的组合, 有时被称为一个端点(endpoint)套接字(socket).

序列号 : 代表包含着该序列号的报文段的数据中的第一个字节. Ack 字段包含的值是该确认号的发送方期待接收的下一个序列号. 即, 最后被成功接收的数据字节的序列号 + 1 . 发送一个 ACK 跟发送任何一个 TCP 报文段的开销是一样的. ACK 位字段通常用于除了初始和末尾报文段之外的所有报文段.

SYN 报文: 客户机发送至服务器的第一个报文段的 SYN 字段被启用. 然后序列号字段, 也包含了在本次连接的这个方向上要使用的第一个序列号, 后续序列号和返回的 ACK 号也要在这个方向上, 称为 初始序列号(initial sequence number, ISN) , 注意, ISN 不是 0 或 1 , 这是出于一种安全措施. 发送在本次连接的这个方向上的数据的第一个字节序列号是 ISN + 1 , 因为 SYN 位字段会消耗一个序列号. 消耗一个序列号, 也意味着使用重传进行可靠传输. 因此, SYN 和应用程序字节(还有 FIN) 是被可靠传输的. 不消耗序列号的 ACK 则不是.

SACK : 选择确认, 它允许接收方告诉发送方它正确地接收到了次序杂乱的数据. 当与一个具有选择重发(selective repeat) 能力的 TCP 发送方搭配, 就可以实现性能的显著改善.

8 位字段(老的实现只理解它们中的最后 6 位, 即上面的 Reserved 后面那部分)

  1. CRW : 拥塞窗口减(发送方降低它的发送速率)
  2. ECE : ECN 回显(发送方接收到了一个更早的拥塞通告)
  3. URG : 紧急(紧急指针字段有效, 很少用)
  4. ACK : 确认(确认字段有效, 建立连接后一般都是启用状态)
  5. PSH : 推送(接收方应尽快给应用程序传送这个数据, 没被可靠地实现或用到. 通常用于说明服务器将不会再发送任何数据. 即发送端缓存为空了)
  6. RST : 重置连接(连接取消, 经常是因为错误)
  7. SYN : 用于初始化一个连接的同步序列号
  8. FIN : 该报文段的发送方已经结束向对方发送数据

TCP 的流量控制由每个端点使用窗口大小字, window来通告一个窗口大小来完成. 这个窗口大小是字节数, 从 ACK 号指定的, 也是接收方想要接收的那个字节开始. 16 位字节, 即窗口字段大小最大为 65535 字节. 但 window scale 窗口缩放 选项可允许对这个值进行绽放.

最常见的选项字段, 就是 MSS (最大段大小). 连接的每个端点, 一般在它发送的第一个报文(即 SYN 报文) 上指定这个选项.MSS 指定该选项的发送者在相反方向上希望接收到的报文段的最大值.

一个连接被建立和终止时, 交换的报文段只包含 TCP 头部(带或不带选项)而没有数据. 如果这个方向上没有数据被传输, 同时通知通信方改变窗口大小(称为一个 窗口更新, window update)

连接管理

TCP 必须检测并修补所有在 IP 层(或下面的层)产生的数据传输问题, 比如丢包, 重复以及错误.

连接的唯一标识 : 四元组(即一对端点或套接字).

建立连接:3 个报文

cleint server
SYN( seq = ISNc)
SYN + ACK(seq = ISNs, ACK=ISNc + 1)
ACK(seq = ISNc + 1, ACK=ISNs + 1)

关闭连接: 4 个报文, (只有当连接双方都完成关闭操作后, 才构成一个完整关闭)

client Server
FIN + ACK(seq = K, ACK = L)
ACK(seq = L, ACK = K + 1)
FIN + ACK(seq = L, ACK = K + 1)
ACK(seq = K, ACK = L + 1)

发送首个 SYN 的一方被认为是主动地打开一个连接.

接收的另一方会接收这个 SYN, 并发送下一个 SYN, 因此它是被动地打开一个连接. 这一方称为服务器.

这 7 个报文是每一个 TCP 连接的正常建立与关闭时的开销.

image-20190708143425090

半关闭

应用程序表明 我已经完成了数据的发送工作, 并发送一个 FIN 给对方, 但我仍然希望接收来自对方的数据, 直到它发送了一个 FIN给我. BSD 的 api shutdown() 就是半关闭. 而 clos() 是完整关闭

image-20190708143342404

同时打开连接

image-20190708143222524

image-20190708143240799

注意它们的交叉

Wireshark 抓包

image-20190708144949958

注意, 上面的 seq 显示的是相对 sequence, 所以才会显示 seq =0, 绝对的 sequence 不会是这个值的.

下面的是修改了 wireshark 的 TCP 协议显示选项后, 显示绝对 sequence 的情况

image-20190708145740323

连接超时

客户端为了建立连接, 而服务器一直没能响应, 会导致客户端频繁地发送 SYN 报文段.

Linux 系统中的 net.ipv4.tcp_syn_retries 表示了一次主动打开申请尝试重新发送 SYN 报文段的最大次数. 通常选择一个较小的值 5.

相应地, net.ipv4.tcp_synack_retries 表示在响应对方的一个主动打开请求时尝试重新发送 SYN + ACK 报文段的最大次数. 默认为 5.

TCP 选项

种类 长度(字节) 名称 描述
0 1 EOL 选项列表结束
1 1 NOP 无操作(用于填充)
2 4 MSS 最大段大小
3 3 WSOPT 窗口缩放因子
4 2 SACK-Permitted 发送者支持 SACK 选项
5 可变 SACK SACK 阻塞(接收到乱序数据)
8 10 TSOPT timestamp 选项
28 4 UTO 用户超时(一段空闲时间后的终止)
29 可变 TCP-AO 认证选项
253 可变 Experimental 保留供实验用
254 可变 Experimental 保留供实验用

每个选项的头一个字节为种类, kind, 表明该选项的类型.

选项的总长度 = (种类 + 长度)个字节.

种类值为 0 或 1 的选项, 仅占用一个字节. 其他的选项会根据种类来确定自身的字节数

注意, TCP 头部的长度应该是 32 bit 的倍数, 因为 TCP 头部长度字段是以此为单位的.

MSS 选项(不包括 TCP 与 IP 的头部)

从对方接收到的最大报文段, 是通信对方在发送数据时能够使用的最大报文段 它只记录 TCP 数据(payload) 的字节数, 而不包括其他相关的 TCP 与 IP 头部.

当建立一条 TCP 连接时, 通信的每一方都要在 SYN 报文段的 MSS 选项中说明自己允许的最大段大小.

MSS 选项一共 3 字节. 其中 1 字节表示种类, 2 字节(16 bit)表示该种类的值.

在没有事先指明的情况下, 最大段大小的默认值为 536 字节. 任何主机都应该能够处理至少 576 字节的 IPv4 数据报. 按最小的 IPv4 和 TCP header 计算, TCP 协议要求在每次发送时的最大段大小为 536 字节, 这样就正好是 576 字节的 IPv4 数据报.(20 + 20 + 536 = 576).

IPv4典型的 MSS 为 1460 . TCP Header 20 字节, IP Header 20 字节. 一共就 1500 字节. (以太网的 MTU).

IPv6 则通常为 1440 字节. 因为 IPv6 的 Header 比 IPv4 的多 20 字节. MSS 为 65535 是一个特殊的数值, 与 IPv6 超长数据报一起用来指定一个表示无限大的有效最大段大小值. 在这种情况下, 发送方的最大段大小等于路径 MTU 的数值 - 60 字节(40 字节 IPv6 Header, 20 字节 TCP Header)

注意, 它不是协商结果, 而是一个限定的数值. 表示自己不愿意在整个连接过程中接收到任何大于该尺寸的报文段.

选择确认选项 SACK

通过接收 SYN (或 SYN + ACK) 报文段的 “允许选择确认” 选项, TCP 通信方会了解到自身具有了发布 SACK 信息的能力.

当接收到乱序的数据时, 它就能提供一个 SACK 选项来描述这些乱序的数据, 从而帮助对方有效地进行重传.SACK 信息保存于 SACK 选项中, 包含了接收方已成功接收的数据块的序列号范围. 每一个范围被称作一个 SACK 块 , 由一对 32 位的序列号表示. 因此, 一个 SACK 选项包含了 n 个 SACK 块, 长度为 ( 8n + 2) 个字节, 增加的 2 个字节, 用于保存 SACK 选项的各类与长度.

由于 TCP 选项的空间是有限的, 因此一个报文段中发送的最大 SACK 块数目为 3. 虽然只有 SYN 报文才能包含 允许选择确认, SACK-Permmited 选项, 但是只要发送方已经发送了该选项, SACK 块就能通过任何报文段发送出去.

窗口缩放 WSOPT 选项

这使得 TCP Header 中的 window 字段段有效地左移. 这样可以将window 值扩大原先的 2^s 倍, s 为比例因子, s 的范围为 [0, 14] 即, 最大可放大原来的 2^14 = 16384 倍.

该选项只能出现于一个 SYN 报文段中, 因此, 当连接建立后, 比例因子是与方向绑定的.

主动打开连接的一方利用自己的 SYN 中发送该选项, 但被动打开连接的一方只能在接收到 SYN 中指出该选项时才能发送.

每个方向的比例因子可各不相同.

它的数值是由 TCP 通信方, 根据 接收缓存的大小 自动选取的. 缓存的大小是由系统设定的, 但应用程序通常都具有改变其大小的能力.

timestamp 选项与防回绕序列号

TSopt, 要求发送方在每一个报文中添加 2 个 4 字节的 timestamp 数值.

接收方将会在确认中反映这些数值, 允许发送方针对每一个接收到的 ACK 估算 TCP 连接的往返时间(由于 TCP 经常利用一个 ACK 来确认多个报文段).

当使用 TS 选项时, 发送方将一个 32 位的数值填充到 TS 字段(称为 TSV 或 TSval)作为TSopt 的第一个部分.而接收方则将收到的 TS 数值原封不动地填充至第二部分的TSER 字段(或 TSecr). 由于包含了 TSopt , TCP 头部的长度会增加 10 字节( 8 字节用于保存 2 个 TS 数值, 另 2 个数值则用于指明选项的数值与长度)

估算一条 TCP 连接的往返时间主要是为了设置重传超时. 重传超时用于告知 TCP 通信方何时应该重新发送可能已经丢失的报文段.

防回绕序列号(PAWS) . 该算法并不要求在发送者与接收者之间有任何形式的时钟同步, 接收者所需要的是保证 TS 值单调增长, 并且每一个窗口的数据至少增加 1.

用户超时选项 UTO

指明了 TCP 发送者在确认对方未能成功接收数据之间愿意等待该数据 ACK 确认的时间. 这人数值也称为 USER_TIMEOUT

这个数值是建议性的. RFC 11222 建议

  • 当 TCP 连接达到 3 次重传阈值时应该通知应用程序(规则 1)
  • 当超时大于 100 秒时应该关闭连接(规则 2)

过长的超时, 会导致资源耗尽.

较短的超时, 会导致连接过早地断开(例如 DDoS).

建立连接的 SYN 报文段, 首个非 SYN 报文段以及任何 USER_TIMEOUT 的数值发生改变的报文段, 都会包含用户超时选项.

它由 15 位的数值 + 1 位的单位部分构成. 单位部分, 1 表示分钟, 0 表示秒.

认证选项

用于增加与替换较早的 TCP-MD5 机制. 很少使用该选项.

路径 MTU 发现

是指经过两台主机之间路径的所有网络报文中最大传输单元的最小值. 知道这, 有助于一些协议(例如 TCP)避免分片.

分组层路径最大传输单元发现(Packetization Layer Path MTU Discovery, PLPMTUD), 该算法能避免对 ICMP 使用, 也能被 TCP 或其他传输协议使用. TCP 常规的路径 MTU 发现过程如下

在一个连接建立时, TCP 使用对外接口的最大传输单元的最小值, 或根据通信对方声明的最大段大小来选择发送方的最大段大小 SMSS.

路径最大传输单元发现 不允许 TCP 发送方有超过另一方所声明的最大段大小的行为.

值得注意的是, 一条连接的两个方向的路径最大传输单元是不同的.

一旦发送方的最大段大小选定了初始值, TCP 通过这条连接发送的所有 IPv4 数据报都会对 DF 位字段进行设置. TCP/IP 没有 DF 位字段, 因此只需要假设所有的数据都已经设置了该字段而不必进行实际操作. 如果接收到PTB(Packet Too Big) 消息, TCP 就会减少段的大小, 然后用修改过的段大小进行重传. 如果在 PTB 消息中包含了下一跳推荐的最大传输单元, 段大小的数值可以设置为下一跳最大传输单元的数值减去 IPv4(或 IPv6)与 TCP Header 的大小.

系统允许的最小报文段大小设置 net.ipv4.route.min_pmtu , pmtu 就是路径 MTU . 即最小路径 MTU.

TCP 状态转换图

查看当前系统所有 TCP 状态及计数
sudo netstat -a -n -t | sed '1,2d' | awk '{print $NF}' | sort | uniq -c

image-20190708175223352

简化版本

image-20190708182113828

TIME_WAIT 状态

也称为 2MSL 等待状态. 在该状态, TCP 将会等待 2 倍于最大段生存期(MSL) 的时间.

每个实现都必须为最大段生存期选择一个数值. 它代表任何报文段在被丢失前在网络中被允许存在的最长时间.

RFC 0793 将MSL 设为 2 分钟. 在 Linux 中, net.ipv4.tcp_fin_timeout 的数值记录了 2MSL 状态需要等待的超时时间(单位为秒).

当一个连接处于 2MSL 等待状态, 那么它在这段时间内将不能被重新使用. 但 BSD 的 API 提供了 SO_REUSEADDR 选项来支持绕开这个限制. 它允许调用者为自己分配本地端口号, 即使这个端口号是某个处于 2MSL 等待状态连接的一部分.

当一个连接处于 2MSL 等待状态, 任何延时到达的报文段都将被丢弃.

静默时间

在崩溃或重启 TCP 协议应当在创建新的连接之前等待相当于一个 MSL 的时间. 该段时间被称为 静默时间 . 不过, 只有极少数实现遵循这点.

这是为了防止一台与处于 TIME_WAIT 状态下的连接相关联的主机崩溃, 然后在 MSL 内重新启动, 并且使用与主机崩溃之前处于 TIME_WAIT 状态的连接相同的 IP 地址与端口号.

重置报文 RST

通常会导致 TCP 连接的快速拆卸.(即 RST 报文中的 TCP 与 IP 头部 4 元组所指定的连接).

对于一个被 TCP 端接收的 RST 报文段, 它的 ACK 位必须被设置, 并且 ACK 号字段的数值必须在正确窗口的范围内.

重置报文段不会令通信另一端做出任何响应 — 它不会被确认.

接收重置报文段的一端会终止连接并通知应用程序当前连接已被重置. 这样通常会造成 “连接被另一端重置” 的错误提示或类似的消息.

终止一条连接

FIN : 正常方法, 这种称为 有序释放 . 因为 FIN 是在之前所有排队数据都已发送后才被发送出去, 通常不会出现丢失数据的情况.

RST : 这种称为 终止释放 . 它提供两大特性

  • 任何排队的数据都将被抛弃. 一个 RST 报文段会被立即发送出去
  • 接收方会说明通信另一端采用了终止的方式, 而不是一次正常关闭

套接字 API 通过将 “逗留于关闭” 选项 SO_LINGER 的数值设置为 0 来实现终止释放. 从本质上说, 这意味着 “不会在终止之前为了确定数据是否到达另一端而逗留任何时间”

TCP 服务器选项

端口号

限制本地IP地址

显式地指定一个本地 IP 地址进行监听. 则只会对该地址的数据进行处理, 其他的本地数据则会拒绝.

限制外部节点

BSD API 并没有提供实现. 可自行检测客户端的 IP 和端口.

当收到一个连接请求时, TCP 模块决定选择的顺序:

本地地址 外部地址 受限于 说明
local_ip.port Foradd.foreign_port 一个客户端 通常不支持
Local_ip.port *.* 一个本地节点 不常见(用于 DNS 服务器)
*.port *.* 一个本地端口 最常见

从本质上说, 如果服务器使用了 IPv6 地址绑定了一个端口号, 那么也就将该端口号应用于了 IPv4 地址.

进入连接队列

在被用于应用程序之前新的连接可能处于下面两个状态

  • 一种是连接尚未完成, 但已接收 SYN (也就是处于 SYN_RCVD 状态)
  • 另一种是连接已完成了三次握手并且处于 ESTABLISHED 状态, 但还未被应用程序接受

因此, OS 通常会使用两个不同的连接队列分别对应上面两种不同的情况.

现代 Linux 中, 将有下面的规则

  • 当一个连接请求到达(即 SYN 报文段), 将会检查系统范围的参数 net.ipv4.tcp_max_syn_backlog (默认为 1000). 如果处于 SYN_RCVD 状态的连接数目超过这一阈值, 进入的连接将会被拒绝
  • 每一个处于侦听状态下的节点都拥有一个固定长度的连接队列. 其中的连接已经被 TCP 完全接受(即三次握手已经完成), 但未被应用程序接受. 应用程序会对这一队列做出限制, 通常称为未完成连接 backlog . backlog 的数目必须在 0 与一个系统指定的最大值之间. 该最大值称为 net.core.somaxconn , 默认值为 128(包含).
  • 如果侦听节点的队列中仍然有空间分配给新的连接, TCP 模块会应答 SYN 并完成连接. 直到接收到三次握手的第三个报文段之后, 与侦听节点相关的应用程序才会知道新的连接. 当客户端的主动打开操作顺利完成之后, 客户端可能会认为服务器已经准备好接收数据, 然而服务器上的应用程序此时可能还未收到关于新连接的通知. 这时, 服务器的 TCP 模块将会把到来的数据存入队列中.
  • 如果队列中已没有足够的空间分配给新的连接, TCP 将会延迟会 SYN 做出响应, 从而给应用程序一个跟上节奏的机会. Linux 它坚持在能力允许的范围内不忽略进入的连接. 如果系统控制变量 net.ipv4.tcp_abort_on_overflow 已被设定, 新进入的连接会被重置报文段重新置位. (默认为 0, 即不发 RST)

在队列溢出的情况下, 发送 RST 报文是不可取的(默认也没开启). 客户端会尝试与服务器联系, 如果它在交换 SYN 时接收到一个 RST 报文段, 那它可能会错误地认为没有服务器存在(而不是认为服务器存在并且十分繁忙). 太忙是一种 “软” 错误, 而不是一种硬性错误.

TCP 连接管理相关的攻击

SYN flooding (SYN 泛洪), 是一种 TCP 拒绝服务攻击, 在这种攻击中一个或多个恶意的客户端产生一系列 TCP 连接尝试(SYN 报文段), 并将它们发送给一台服务器, 它们通常采用 “伪造” 的(例如, 随机选择) 源 IP 地址. 服务器会为每一条连接分配一定数量的连接资源. 由于连接尚未完全建立, 服务器为了维护大量的半打开连接会耗尽自身内存后拒绝为后续的合法连接请求服务.

一种解决机制是 SYN cookies(RFC 4987). 它的主要思想是, 当一个 SYN 到达时, 这条连接存储的大部分信息都会被编码并保存在 SYN+ACK 报文段的序列号字段. 采用 SYN cookies 的目标主机不需要为进入的连接请求分配任何存储资源, 只有当 SYN + ACK 报文段本身被确认后(并且已返回初始序列号)才会分配真正的内存.

Linux 采用的技术:

服务器接收 到一个 SYN 后会采用下面的方法设置初始序列号(保存于 SYN + ACK 报文段, 供于客户端)的数值 : 首 5 位是 t 模 32 的结果, 其中 t 是一个 32 位的计数器, 每隔 64 秒增加 1; 接着 3 位是对服务器最大段大小(8 种可能之一)的编码值; 剩余 24 位保存了 4 元组与 t 值的散列值. 该数值是根据服务器选定的散列加密算法计算得到的.

采用 SYN + cookies 方法时, 服务器总是以一个 SYN + ACK 报文段作为响应. 在接收 到 ACK 后, 如果根据其中的 t 值可以计算出与加密的散列值相同的结果, 那么服务器才会为该 SYN 重新构建队列.

这种技术的缺陷是

  • 需要对最大段大小进行编码, 所以禁止使用任意大小的报文段.
  • 由于计数器会回绕, 连接建立过程会因周期非常长(长于 64 秒)而无法正常工作

所以, 这一功能未作为默认设置.

另一种是与路径最大 MTU 发现过程相关. 攻击者伪造一个 ICMP PTB 消息. 该消息包含一个非常小的 MTU值(例如 68 字节). 这样就迫使受害的 TCP 尝试采用非常小的数据包来填充数据, 从而大大降低了它的性能.

还有 序列号攻击 , 欺骗攻击

TCP 数据流与窗口管理

延时确认

  • 利用 TCP 的累积 ACK 字段实现该功能. 累积确认可允许 TCP 延迟一段时间发送 ACK, 以便将 ACK 和相同方向上需要传的数据结合发送. 常用于批量数据传输.
  • RFC 1122 指出, ACK 延迟的时延应小于 500 ms. 实践中时延最大取 200 ms

Nagle 算法

  • 要求, 当一个 TCP 连接中有在传数据(即那些已发送但还未经确认的数据)小的报文段(长度小于 SMSS) 就不能被发送, 直到所有的在传数据都收到 ACK. 并且, 在收到 ACK 后, TCP 需要收集这些小数据, 将其整合到一个报文段中发送. 这种方法迫使 TCP 遵循停等(stop and wait) 规程 — 只有等待收到所有在传数据的 ACK 后才能继续发送.
  • 该算法实现了自时钟(self clocking) 控制: ACK 返回越快, 数据传输也越快. 在相对高延迟的广域网中, 需要减少微型报的数目, 该算法使得单位时间内发送的报文段数目更少. 也就是说, RTT 控制着发包速率.
  • 但每发送一组请求和响应包需要等待一个 RTT. 这就加长了整个传输过程.
  • Nagle 算法做出了一种折中 : 传输的包数目更少而长度更大, 但同时传输时延也更长.

image-20190709170817188

  • 时延ACK 与 Nagle 算法结合 (性能效率降低) : 客户端延时 ACK, 而服务器端启用 Nagle 算法会导致某种程度的死锁(两端互相等待对方做出行动). 不过, 这不是永久死锁, 在延时 ACK 计时器超时后会解除.
  • 禁用 Nagle 算法. (典型的是那些要求延时尽量小的应用, 如多人网络游戏). 设置 TCP_NODEPLAY 选项即可.

窗口管理

window 的数值, 表示发送该窗口信息的通信方为即将到来的新数据预留的存储空间. 如果为 0, 表示要发送端完全停止新数据的发送. 单位为字节. byte.

滑动窗口

TCP 连接的每一端都可收发数据. 它是通过一组 窗口结构(window structure) 来维护的. 每个 TCP 活动连接的两端都维护一个 发送窗口结构, send window structure接收窗口结构, receive window structure.

image-20190709180657705

运动术语

  • 关闭 close : 窗口左边界右移. 当已发送数据得到 ACK 确认时, 窗口会减小.
  • 打开 open : 窗口右边界右移. 使得可发送数据量增大.
  • 收缩 shrink : 窗口右边界左移. RFC1122 并不支持这一做法, 但 TCP 必须以处理这一问题.

image-20190709181255052

零窗口

当窗口大小恢复为非零值, 即接收端重新获得可用空间时, 会给发送端传输一个 窗口更新, window update , 告知其可继续发送数据. 这样的窗口更新通常都不包含数据, 为 纯 ACK, 不能保证其传输的可靠性. 因此 TCP 必须有相应措施能处理这类丢包. 这时, 发送端采用一个持续计时器间歇性地查询接收端, 看其窗口是否已增长. 持续计时器会触发 窗口探测 window probe (包含 1 字节的数据) 的传输, 强制要求接收端返回 ACK (其中包含了 window 大小).

拥塞控制

基本方法是当有理由认为网络即将进入拥塞状态(或已经由于拥塞而出现路由丢包情况)时减缓 TCP 传输.

难点在于怎样准确地判断何时需要减缓且如何减缓 TCP 传输, 以及何时恢复其原有的速度

路由器因无法处理高速率到达的流量而被迫丢弃数据信息的现象称为拥塞.

减缓 TCP 发送

反映网络传输能力的变量称为 拥塞窗口, congestion window, 记作 cwnd .

发送端实际可用窗口 W 就是接收端通知窗口 awnd 和 拥塞窗口 cwnd 的较小者 : W = min(cwnd, awnd)

TCP 发送端发送的数据中, 还没有收到 ACK 回复的数量不能多于 W (以包或字节为单位). 这种已发出但还未经确认的数据量大小有时称为 在外数据值, flight size , 它总是小于等于 W . 但实际上, awnd 和 cwnd 的数值会随着时间变化而改变.

W 的值不能过大或过小 — 我们希望其接近 带宽延迟积, Bandwidth-Delay Product, BDP, 也称作 最佳窗口大小, optimal window size . W 反映网络中可存储的待发送数据量大小, 其计算值等于 RTT 与链路中最小通行速率(即发送端与接收端传输路径中的瓶颈)的乘积.

相关的算法

  • 慢启动(新的TCP 建立连接或检测到由重传超时 RTO 导致的丢包时, 需要执行慢启动)
    • TCP 以发送一定数目的数据段开始慢启动(在交换 SYN 后), 称为 初始窗口(Initial Window, IW)
    • IW = 2 * SMSS 且小于等于 2 个数据段. (当 SMSS > 2190 字节)
    • IW = 3 * SMSS 且小于等于 3 个数据段. (当 2190 >= SMSS > 1095 字节)
    • IW = 4 * SMSS 且小于等于 4 个数据段. (其他)
    • 慢启动阈值(slow start threshold, ssthresh, 这个数据可用 ip tcp_metrics 命令查看)
    • 接收到一个数据段的 ACK 后, 通常 cwnd 的值会增加到 2(开始假设为 1的话). 接着发送两个数据段. 如果也成功收到, 则 cwnd 会由 2 变成 4, 由 4 变成 8, 以此类推. 在 k 轮之后 W 的值为 W = 2^k. 最终 cwnd 增加到很大, 会导致网络瘫痪. 当出现时, cwnd 会大幅度减小(原值的一半). 这是 TCP 由慢启动阶段至拥塞避免阶段的转折点, 与 cwnd 和 ssthred 相关.
  • 拥塞避免
    • 一旦确立慢启动阈值, TCP 会进入拥塞避免阶段, cwnd 每次的增长值近似于成功传输的数据段大小. 更新公式如下
    • CWND(t+1) = CWND(t) + SMSS * SMSS / CWND(t)

它们不是同时运行的, 在同一时刻只会有一种算法, 但可以相互切换. 慢启动与拥塞避免的选择:

  • 当 cwnd < ssthresh , 使用慢启动算法

  • 当 cwnd > ssthresh , 使用拥塞避免.

  • 当 cwnd = ssthresh 时, 任何一种算法都可以使用.

image-20190711150348875

image-20190711151312304

keepalive 机制

如果在一段时间(称为 keepalive time) 内连接处于非活动状态, 开启keepalive 的一端将向对方发送一个探测报文.

如果发送端没有收到响应报文, 那么经过一个已经提前配置好的时间间隔(keepalive interval) , 将继续发送探测报文.

直到发送探测报文的次数达到配置的探测数(keepalive probe) , 这时对方主机将被确认为不可到达, 连接也将被中断.

探测报文为一个空报文(或只包含一个字节). 它的 seq 为对方主机发送的 ACK 报文的最大序列号减 1.

因为这一序列号的数据已经被成功接收, 所以不会对到达的报文段造成影响, 但探测报文返回的响应可以确定连接是否仍在工作. 探测及其响应报文都不包含任何新的有效数据, 当它们丢失时也不会进行重传. RFC1122 指出, 仅凭一个没有响应的探测报文不能判断连接是否已经停止工作. 这就是 keepalive probe 参数作用了.

上面的参数在 Linux 中为

net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200

开启功能的一端可以发现对方处于以下四种状态之一

  1. 对方主机仍在工作, 并且可以到达.

  2. 对方主机已崩溃, 包括已经关闭或正在重新启动. 在probes 次数之后还没有收到响应就断开连接.

  3. 对方主机崩溃但已重启. 这时如果有探测报文则会收到一个 RST 报文, 然后断开连接.

  4. 对方主机仍在工作, 但请求不能到达. 这种情况与 2 相同. TCP 不能分区这两个状态. 结果是探测报文都没有收到响应.

RFC1122 明确给出了用户使用keepalive 功能的限制.

keepalive time 必须是可配置的, 而且默认不能小于 2 小时.

此外, 除非应用层请求开启 keepalive , 否则不能使用该功能.

对于 Linux, 没有经过应用层的请求, 则不会提供 keepalive 功能.

杂项

使用 ping 来发现 PMTU

# Linux , -M do 参数表示使用执行 MTU 发现. -s 数据大小
ping -M do -s 1460 www.qq.com

如果数据包过大, 会报类似下面的错误信息
ping: local error: Message too long, mtu=1500

Linux network

Github

查看 socket 统计

cat /proc/net/sockstat

TCP 中的 tw 表示 TIME_WAIT 数

mem 表示system page (即系统页数)