0%

网络相关知识:协议栈

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
2
3
4
5
struct net_device {
......
struct list_head ptype_all; /* 保存所有协议处理回调的地方(设备有关) */
......
}

添加协议的 API 如下:

1
2
3
4
5
6
7
8
9
void dev_add_pack(struct packet_type *pt)
{
struct list_head *head = ptype_head(pt);

spin_lock(&ptype_lock);
list_add_rcu(&pt->list, head);
spin_unlock(&ptype_lock);
}
EXPORT_SYMBOL(dev_add_pack);

其中需要传入的 packet_type 结构体用于描述一个协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct packet_type {
__be16 type; /* This is really htons(ether_type). */
bool ignore_outgoing;
struct net_device *dev; /* NULL is wildcarded here */
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);
void (*list_func) (struct list_head *,
struct packet_type *,
struct net_device *);
bool (*id_match)(struct packet_type *ptype,
struct sock *sk);
void *af_packet_priv;
struct list_head list;
};
  • 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
2
3
4
5
6
7
void dev_remove_pack(struct packet_type *pt)
{
__dev_remove_pack(pt);

synchronize_net(); /* 保证在dev_remove_pack返回的时候,这个移除了的packet_type没有在内核中使用 */
}
EXPORT_SYMBOL(dev_remove_pack);

数据包的处理流程

对于 ptype_baseptype_all 两个类型的协议 Container(容器),协议 Handler 的调用方法是相似的:

  • 遍历其中的双向链表,直到找到了符合条件的 packet_type
  • 可以使用 deliver_skb 间接调用 packet_type->func
  • 或者直接调用 packet_type->func

ptype_baseptype_all 的不同之处在于:

  • ptype_all 本身就是一个双向链表,可以直接遍历
  • ptype_base 则是一个包含了双向链表的哈希表, 在遍历之前需要根据 skb->protocol 来计算找到待遍历的双向链表

当网卡收到数据包后,它会将数据包从网卡硬件缓存转移到服务器内存中(具体为 sk_buffer),然后触发一个中断来通知内核进行处理

内核会执行如下函数来处理 sk_buffer 中的数据包:

1
2
3
4
5
6
7
int netif_receive_skb(struct sk_buff *skb)
{
trace_netif_receive_skb_entry(skb);

return netif_receive_skb_internal(skb);
}
EXPORT_SYMBOL(netif_receive_skb);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static int netif_receive_skb_internal(struct sk_buff *skb)
{
int ret;

net_timestamp_check(netdev_tstamp_prequeue, skb); /* 检查timestamp */

if (skb_defer_rx_timestamp(skb))
return NET_RX_SUCCESS;

if (static_branch_unlikely(&generic_xdp_needed_key)) {
int ret;

preempt_disable();
rcu_read_lock();
ret = do_xdp_generic(rcu_dereference(skb->dev->xdp_prog), skb);
rcu_read_unlock();
preempt_enable();

if (ret != XDP_PASS)
return NET_RX_DROP;
}

rcu_read_lock(); /* RCU读锁(读者不受限制,写者阻塞) */
#ifdef CONFIG_RPS
if (static_key_false(&rps_needed)) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
int cpu = get_rps_cpu(skb->dev, skb, &rflow);

if (cpu >= 0) {
ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
rcu_read_unlock();
return ret;
}
}
#endif
ret = __netif_receive_skb(skb);
rcu_read_unlock();
return ret;
}
  • netif_receive_skb_internal 只是对数据包进行了 RPS(增加服务器的负载均衡,优化吞吐率)的处理,然后调用 __netif_receive_skb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int __netif_receive_skb(struct sk_buff *skb)
{
int ret;

if (sk_memalloc_socks() && skb_pfmemalloc(skb)) {
unsigned int noreclaim_flag;
noreclaim_flag = memalloc_noreclaim_save();
ret = __netif_receive_skb_one_core(skb, true);
memalloc_noreclaim_restore(noreclaim_flag);
} else
ret = __netif_receive_skb_one_core(skb, false);

return ret;
}
  • 进行简单检查后就调用了 __netif_receive_skb_one_core
1
2
3
4
5
6
7
8
9
10
11
static int __netif_receive_skb_one_core(struct sk_buff *skb, bool pfmemalloc)
{
struct net_device *orig_dev = skb->dev;
struct packet_type *pt_prev = NULL;
int ret;

ret = __netif_receive_skb_core(skb, pfmemalloc, &pt_prev);
if (pt_prev)
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev); /* 调用协议处理 */
return ret;
}
  • 然后调用核心函数 __netif_receive_skb_core,返回对应的 packet_type 并调用 packet_type->func
  • 核心函数 __netif_receive_skb_core 的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc,
struct packet_type **ppt_prev)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t *rx_handler;
struct net_device *orig_dev;
bool deliver_exact = false;
int ret = NET_RX_DROP;
__be16 type;

net_timestamp_check(!netdev_tstamp_prequeue, skb);

trace_netif_receive_skb(skb);

orig_dev = skb->dev;

skb_reset_network_header(skb);
if (!skb_transport_header_was_set(skb))
skb_reset_transport_header(skb);
skb_reset_mac_len(skb);

pt_prev = NULL;

another_round:
skb->skb_iif = skb->dev->ifindex;

__this_cpu_inc(softnet_data.processed);

if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
skb = skb_vlan_untag(skb);
if (unlikely(!skb))
goto out;
}

if (skb_skip_tc_classify(skb))
goto skip_classify;

if (pfmemalloc)
goto skip_taps;

/* ptype_all容器中协议处理(tcpdump抓包就在这里) */
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}

/* dev->ptype_all容器中协议处理(和设备有关) */
list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}

skip_taps:
#ifdef CONFIG_NET_INGRESS
if (static_branch_unlikely(&ingress_needed_key)) {
skb = sch_handle_ingress(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;

if (nf_ingress(skb, &pt_prev, &ret, orig_dev) < 0)
goto out;
}
#endif
skb_reset_tc(skb);
skip_classify:
if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
goto drop;

/* 对vlan报文的处理 */
if (skb_vlan_tag_present(skb)) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
if (vlan_do_receive(&skb))
goto another_round;
else if (unlikely(!skb))
goto out;
}

/* 调用接收设备的rx_handler */
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED:
ret = NET_RX_SUCCESS;
goto out;
case RX_HANDLER_ANOTHER:
goto another_round;
case RX_HANDLER_EXACT:
deliver_exact = true;
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}

if (unlikely(skb_vlan_tag_present(skb))) {
if (skb_vlan_tag_get_id(skb))
skb->pkt_type = PACKET_OTHERHOST;
skb->vlan_tci = 0;
}

/* 根据skb->protocol传递给上层协议(其实这就是"sk_buff=>packet_type"的过程) */
type = skb->protocol;

if (likely(!deliver_exact)) { /* 根据全局定义的协议处理报文 */
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&ptype_base[ntohs(type) &
PTYPE_HASH_MASK]); /* 利用skb->protocol在ptype_base中选择对应的双向链表 */
}

deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&orig_dev->ptype_specific); /* 根据设备上注册的协议进行处理 */

if (unlikely(skb->dev != orig_dev)) { /* 如果设备发生变化,那么还需要针对新设备的注册协议进行处理 */
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&skb->dev->ptype_specific);
}

if (pt_prev) {
if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
goto drop;
*ppt_prev = pt_prev; /* 把查找到的packet_type传出 */
} else {
drop:
if (!deliver_exact)
atomic_long_inc(&skb->dev->rx_dropped);
else
atomic_long_inc(&skb->dev->rx_nohandler);
kfree_skb(skb);
ret = NET_RX_DROP;
}

out:
return ret;
}

__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 完成对数据包的处理