TCP/IP协议
socket通讯中一个包的组成
假设女朋友给你邮寄一封浪漫的信,收信人:男朋友,收信地址:广东省深圳市南山区南山小区1栋1单元401。
- 邮政收到这封信后会将信件送至广东省深圳市南山区南山小区的物业管理处,这个地址是IP头的ip地址
- 物业会把信送至1栋1单元401居室,这个地址是MAC头的mac地址;
- 家里会把信件准确的给男朋友这个人,这个是TCP/UDP头的port
MAC帧
#define ETH_ALEN 6
struct ethhdr {
uint8_t h_dest[ETH_ALEN]; /* destination eth addr */
uint8_t h_source[ETH_ALEN]; /* source ether addr */
uint16_t h_proto; /* packet type ID field */
};
uint32_t check;
- h_dest: 接收端mac地址
- h_source: 发送端mac地址
- h_proto: 上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp
- check: 帧尾校验和
IP协议头
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD) //小端
uint8_t ihl:4,
uint8_t version:4;
#elif defined (__BIG_ENDIAN_BITFIELD) // 大端
uint8_t version:4,
uint8_t ihl:4;
#endif
uint8_t tos;
uint16_t tot_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t check;
uint32_t saddr;
uint32_t daddr;
/*The options start here. */
};
- version: IP协议的版本。对于IPv4来说值是4
- ihl: ip header length,协议头的长度,单位为字节
- tos: type of service,其中前3bit为优先权字段(Precedence,现已被忽略)。第8bit保留未用。4~7bit分别代表延迟、吞吐量、可靠性和花费。当它们取值为1时分别代表要求最小时延、最大吞吐量、最高可靠性和最小费用。这4比特的服务类型中只能置其中1bit为1。可以全为0,若全为0则表示一般服务。服务类型字段声明了数据报被网络系统传输时可以被怎样处理。例如:TELNET协议可能要求有最小的延迟,FTP协议(数据)可能要求有最大吞吐量,SNMP协议可能要求有最高可靠性,NNTP(Network News Transfer Protocol,网络新闻传输协议)可能要求最小费用,而ICMP协议可能无特殊要求(4比特全为0)。实际上,大部分主机会忽略这个字段,但一些动态路由协议如OSPF(Open Shortest Path First Protocol)、IS-IS(Intermediate System to Intermediate System Protocol)可以根据这些字段的值进行路由决策。
- tot_len: 指协议头和数据的长度,单位为字节
下面的2个字段则描述如何实现分片:
- id: 其初始值是随机的,每发送一个数据报其值就加1。同一个数据报的所有分片id相同
- frag_off: 第1bit保留;第2bit表禁止分片(DF),在此情况下若IP数据报超过MTU,IP模块将丢弃数据报并返回一个ICMP出错报文;第3bit表示除了数据报的最后一个分片,其他分片都要把它设置为1。4~16bit分片相对原始IP数据报数据部分的偏移。除了最后一个IP数据报分片外,每个IP分片的数据部分的长度都必须是8的整数倍
- ttl: time to live,数据报到达目的地之前允许经过的路由器跳数。TTL值被发送端设置,常设置为64。数据报在转发过程中每经过一个路由该值就被路由器减1.当TTL值为0时,路由器就将该数据包丢弃,并向源端发送一个ICMP出错报文。TTL可以防止数据报陷入路由循环
- protocol: 协议类型,ICMP为1,TCP为6,UDP为17
- check: 接收端使用CRC算法校验
- saddr: source address,发送端ip
- daddr: destination address,接收端ip
-
注释部分:
选项:可变长的可选信息,最多包含40字节,保持协议头4字节对齐。选项字段很少被使用。可用的IP可选项有:
a. 记录路由: 记录数据包途径的所有路由的IP,这样可以追踪数据包的传递路径
b. 时间戳: 记录每个路由器数据报被转发的时间或者时间与IP地址对,这样就可以测量途径路由之间数据报的传输的时间
c. 松散路由选择: 指定路由器的IP地址列表数据发送过程中必须经过所有的路由器
d. 严格路由选择: 数据包只能经过被指定的IP地址列表的路由器
e. 上层协议(如TCP/UDP)的头部信息
TCP
struct tcphdr {
uint16_t source;
uint16_t dest;
uint32_t seq;
uint32_t ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD) // 小端
uint16_t res1:4,
uint16_t doff:4,
uint16_t fin:1,
uint16_t syn:1,
uint16_t rst:1,
uint16_t psh:1,
uint16_t ack:1,
uint16_t urg:1,
uint16_t ece:1,
uint16_t cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD) // 大端
uint16_t doff:4,
uint16_t res1:4,
uint16_t cwr:1,
uint16_t ece:1,
uint16_t urg:1,
uint16_t ack:1,
uint16_t psh:1,
uint16_t rst:1,
uint16_t syn:1,
uint16_t fin:1;
#endif
uint16_t window;
uint16_t check;
uint16_t urg_ptr;
/*The options start here. */
};
struct options {
uint8_t type;
uint8_t length;
uint8_t value[N]; // N总是与length相等
}
- source: 发送端端口
- dest: 接收端端口
- seq: 发送序列,初始值随机数(三次握手交换),发送多少字节的数据增加多少。如:初始值为ISN,第一次发送800字节,该值为ISN+1;第二次发送900字节,该值为ISN+801;第三次发送1000,该值为ISN+1701
- ack_seq: 返回确认收到数据。如:接上面例子收到seq==ISN+1701,返回给发送端ack_seq==ISN+2701。也就是下一次seq的值
- doff: 数据的偏移位置。也可以认为协议头的长度,单位为字节
- res1: 保留字段
- cwr: 保留字段
- ece: 保留字段
- urg: 紧急数据标识,与urg_ptr相关
- ack: 是ack包置为1,与ack_seq有关
- psh: 是应用发送的数据置为1,与seq有关
- rst: 一般没有此服务或连接错误的包会置为1。
- syn: TCP三次握手置为1
- fin: TCP四次挥手置为1
- window: 窗口。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据。ack包也不是每接收1个就回一个,假如发送了4个包,ack告知发送端第四个包收到了,发送端就认为4个包都收到了,具体看TCP策略
- check: 校验和
- urg_ptr: 紧急指针
UDP
struct udphdr {
uint16_t source;
uint16_t dest;
uint16_t len;
uint16_t check;
};
- source: 发送端端口
- dest: 接收端端口
- len: 指协议头和数据的长度,单位为字节
- check: 校验和
MTU
什么是MTU
Maximum Transmission Unit,缩写MTU,中文名是:最大传输单元。
怎么获取MTU的值
通过在IP报头中设置不分片DF(Don't Fragment)标志来探测路径中的MTU值, 如果路径中设备的MTU值小于此数据包长度,并且发现DF标志,就会发回一个Internet控制消息协议(ICMP)(类型3、代码4需要分片的消息ICMP_FRAG_NEEDED),消息中包含它可接受的MTU值。
获取MTU的好处
获取MTU的好处是传输过程中不拆包,提高传输效率。以太网默认是1500字节