TCP/IP 协议栈简述
TCP/IP 协议栈是一系列网络协议的总和,是构成网络通信的核心骨架,采用4层结构,分别是:
- 应用层:
- 向用户态程序提供网络接口
- 例如:域名系统DNS协议、HTTP超文本传送协议、Telnet远程登录协议、SNMP简单网络管理协议
- 传输层:
- 位于通信部分的最高层,用户功能的最底层,用于为运行在不同主机上的进程之间提供逻辑通信
- 例如:传输控制协议TCP - Transmission Control Protocol,用户数据报协议UDP - User Datagram Protocol
- 网络层:
- 进一步管理网络中的数据通信,将数据从源端经过若干中间节点传送到目的端
- 例如:ARP协议,IP协议,ICMP协议,IGMP协议
- 链路层:
- 提供透明可靠的数据传送基本服务
- 例如:以太网协议
梳理一下每层模型的职责:
- 链路层:对“0”和“1”进行分组,定义数据帧,确认主机的物理地址,传输数据
- 网络层:定义IP地址,确认主机所在的网络位置,并通过IP进行MAC寻址,对外网数据包进行路由转发
- 传输层:定义端口,确认主机上应用程序的身份,并将数据包交给对应的应用程序
- 应用层:定义数据格式,并按照对应的格式解读数据
网络协议的注册/注销
协议在内核中用 packet_type
来表示,其拥有不同的容器:
- 每个协议在系统初始化或者相应模块加载的时候添加到内核中的一个大小为16的哈希表
ptype_base
中,其中哈希表中的每个元素都是一个双向链表:
1 | struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly; /* 保存所有支持的3层协议(网际协议)处理函数的地方 */ |
- 另一种协议容器是
ptype_all
,它直接就是一个双向链表:
1 | struct list_head ptype_all; /* 保存所有协议处理回调的地方(设备无关) */ |
- 还有一种协议容器是
struct net_device
中的一个条目,也是一个双向链表:
1 | struct net_device { |
添加协议的 API 如下:
1 | void dev_add_pack(struct packet_type *pt) |
其中需要传入的 packet_type
结构体用于描述一个协议:
1 | struct packet_type { |
type
:协议代码,表明了协议类型dev
:设备,指明了在哪个设备上使能该协议(dev
将 NULL 视为通配符,表示任意设备)func
:对应协议的 Handler(处理程序,例如:在网卡收到数据后,netif_receive_skb
将会根据skb->protocol
来调用相应的 func 来处理 skb)af_packet_priv
:为 PF_PACKET 类型的 socket 使用,指向该packet_type
的创建者相关的 sock 数据结构list
:协议链表头
因此,协议的注册由两步完成:
- 对
packet_type
进行初始化 - 调用
dev_add_pack
把目标协议加入到ptype_base
相应的,协议的注销需要调用如下 API:
1 | void dev_remove_pack(struct packet_type *pt) |
数据包的处理流程
对于 ptype_base
和 ptype_all
两个类型的协议 Container(容器),协议 Handler 的调用方法是相似的:
- 遍历其中的双向链表,直到找到了符合条件的
packet_type
- 可以使用
deliver_skb
间接调用packet_type->func
- 或者直接调用
packet_type->func
ptype_base
和 ptype_all
的不同之处在于:
ptype_all
本身就是一个双向链表,可以直接遍历ptype_base
则是一个包含了双向链表的哈希表, 在遍历之前需要根据skb->protocol
来计算找到待遍历的双向链表
当网卡收到数据包后,它会将数据包从网卡硬件缓存转移到服务器内存中(具体为 sk_buffer
),然后触发一个中断来通知内核进行处理
内核会执行如下函数来处理 sk_buffer
中的数据包:
1 | int netif_receive_skb(struct sk_buff *skb) |
1 | static int netif_receive_skb_internal(struct sk_buff *skb) |
netif_receive_skb_internal
只是对数据包进行了 RPS(增加服务器的负载均衡,优化吞吐率)的处理,然后调用__netif_receive_skb
1 | static int __netif_receive_skb(struct sk_buff *skb) |
- 进行简单检查后就调用了
__netif_receive_skb_one_core
:
1 | static int __netif_receive_skb_one_core(struct sk_buff *skb, bool pfmemalloc) |
- 然后调用核心函数
__netif_receive_skb_core
,返回对应的packet_type
并调用packet_type->func
- 核心函数
__netif_receive_skb_core
的源码如下:
1 | static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc, |
__netif_receive_skb_core
函数主要有几个处理:
- vlan 报文的处理,主要是循环把 vlan 头剥掉(Virtual Local Area Network 虚拟局域网)
- 交给 rx_handler 处理(例如:OVS,linux bridge 等)
ptype_all
处理(例如:抓包程序,raw socket 等)ptype_base
处理,并交给协议栈(例如:ip、arp,rarp 等)
其中我们只需要关注与有关 ptype_base
的部分,这里才是对网络协议的处理:
- 程序会通过
skb->protocol
找到ptype_base
中对应的双向链表 - 遍历这个双向链表,直到
packet_type->type == sk_buff->protocol
- 然后调用
packet_type->func
完成对数据包的处理