Skip to content

Latest commit

 

History

History
109 lines (92 loc) · 4.14 KB

2.md

File metadata and controls

109 lines (92 loc) · 4.14 KB
title date
Let's code a TCP/IP stack, 2 - IPv4 & ICMPv4
2018-12-28T23:53:00.000Z

原文地址:http://www.saminiir.com/lets-code-tcp-ip-stack-2-ipv4-icmpv4/

ICMP: 互联网控制消息协议,它用于 TCP/IP 网络中发送控制消息,提供可能出现在通讯过程中的各种问题反馈,ping 就是基于 ICMP 的

IPv4

IPv4 是一种无连接协议,它会尽最大努力交付数据包,就是说它不保证发送包的顺序,也不保证包被送达目的地。

如果需要保证通信的可靠性,需要在其上层做相应的处理,如 TCP 协议。

Header Format

报头结构如下:

struct iphdr {
    // version 字段占4bit,表示协议的版本,通信双方使用的版本必须一致。对于IPv4,字段的值是4
    uint8_t version : 4;
    // internet header length => ihl,表示报头有多少个 32 位字,ihl 最大值为 1111 -> 15,因此最多能放 15 * 32 bit = 15 * 4 byte
    uint8_t ihl : 4;
    // type of service
    uint8_t tos;
    // 报文总长,这个字段占 16 bit,因此其最大值为 65535 bytes,大一些的报文会被分割成较小的报文,以满足不同通信接口的最大传输单元(MTU)
    uint16_t len;
    // 标识符,用来唯一标识一个报文的所有分片,该字段的值是在发送方填写的一个自增的值,接收方可以使用这个值对分片重新排列。
    uint16_t id;
    // 用于控制和识别分片,发送方指定是否可以对报文进行分片,具体的分片方式等
    uint16_t flags : 3;
    // 分片偏移,表明当前分片相对于原始报文开头的偏移量
    uint16_t frag_offset : 13;
    // 存活时间,这个是为了防止报文陷入路由环路而导致其一直存在。
    uint8_t ttl;
    // 协议,定义了该报文数据区使用的协议
    uint8_t proto;
    // 首部检验和,用来做完整性验证
    uint16_t csum;
    // 源地址
    uint32_t saddr;
    // 目标地址
    uint32_t daddr;
} __attribute__((packed));

ICMPv4

中文全称:互联网控制消息协议 英语:Internet Control Message Protocol,缩写:ICMP

IP 协议缺乏可靠性,需要通过某种方式告知通信方可能的错误情况。ICMP 就是用来诊断网络问题的,比如当网关不可达时,ICMP 会告知消息的发起者 “Gateway Unreachable”。

Header Format

struct icmp_v4 {
  // ICMP 类型,标识错误报文
  uint8_t type;
  // 进一步划分 ICMP 类型,用来查找错误原因
  uint8_t code;
  // 校验码,用于检查数据的错误
  uint16_t csum;
  uint8_t data[];
} __attribute__((packed));

消息及其处理

实际的 ICMP 报文中,会有查询信息和错误信息。我们先看下请求/回复消息:

struct icmp_v4_echo {
  // id 由发送方指定,决定由哪个进程进行应答
  uint16_t id;
  // seq 是一个从 0 开始的序号,每当有一个新的 echo 请求,就 +1。应答的时候需要把这个信息带回来,已确定这条消息是否在传输过程中消失或者重新排序
  uint16_t seq;
  // data 可选字段,里面经常会包含 echo 的时间戳,以方便估算 hosts 之间的往返时间
  uint8_t data[];
}__attribute__((packed));

举个 ICMPv4 中目的地不可达的错误信息的例子:

struct icmp_v4_dst_unreachable {
  // 第一个 8 位字节未使用
  uint8_t unused;
  // 原始数据报的长度
  uint8_t len;
  uint16_t var;
  // 原始数据包
  uint8_t data[];
}__attribute__((packed));

ping 一下 shimo.im 看看结果:

PING shimo.im (47.95.42.129): 56 data bytes
64 bytes from 47.95.42.129: icmp_seq=0 ttl=93 time=5.368 ms
64 bytes from 47.95.42.129: icmp_seq=1 ttl=93 time=29.821 ms
64 bytes from 47.95.42.129: icmp_seq=2 ttl=93 time=6.258 ms
64 bytes from 47.95.42.129: icmp_seq=3 ttl=93 time=6.776 ms
64 bytes from 47.95.42.129: icmp_seq=4 ttl=93 time=22.077 ms
64 bytes from 47.95.42.129: icmp_seq=5 ttl=93 time=7.118 ms

--- shimo.im ping statistics ---
6 packets transmitted, 6 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 5.368/12.903/29.821/9.507 ms