1.摘要
这是《翻译:理解TCP/IP网络栈&编写网络应用》的下篇,文章中会通过讲解TCP的代码实现帮助大家理解发送、接收数据的流程,也描述了一些网卡、驱动等网络栈底层的原理。
原文地址:原文地址
原作者:Hyeongyeop Kim
2.数据结构
以下是一些关键数据结构。我们了解一下这些数据结构再开始查看代码。
2.1.sk_buff_structure
首先,sk_buff结构或skb结构代表一个数据包。图6展现了sk_buff中的一些结构。随着功能变得更强大,它们也变得更复杂了。但是还是有一些任何人都能想到的基本功能。
图6:数据包结构
2.1.1.包含数据和元数据
这个结构直接包含或者通过指针引用了数据包。在图6中,一些数据包(从Ethernet到Buffer部分)使用了指针,一些额外的数据(frags)引用了实际的内存页。
一些必要的信息比如头和内容长度被保存在元数据区。例如,在图6中,mac_header、network_header和transport_header都有相应的指针,指向链路头、IP头和TCP头的起始地址。这种方式让TCP协议处理过程变得简单。
2.1.2.如何增加或删除头
数据包在网络栈的各层中上升或下降时会增加或删除数据头。为了更有效率的处理而使用了指针。例如,要删除链路头只需要修改head pointer的值。
2.1.3.如何合并或切分数据包
为了更有效率的执行把数据包增到或从socket缓冲区中删除这类操作而使用了链表,或者叫数据包链。next和prev指针用于这个场景。
2.1.4.快速分配和释放
无论何时创建数据包都会分配一个数据结构,此时会用到快速分配器。比如,如果数据通过10Gb的以太网传输,每秒会有超过一百万个对象被创建和销毁。
3.TCP控制块(TCP Control Block)
其次,有一个表示TCP连接的数据结构,之前它被抽象的叫做TCP控制块。Linux使用了tcp_sock这个数据结构。在图7中,你可以看到文件、socket和tcp_socket的关系。
图7:TCP Connection结构
当系统调用发生后,它会找到应用程序在进行系统调用时使用的文件描述符对应的文件。对Unix系的操作系统来说,文件本身和通用文件系统存储的设备都被抽象成了文件。因此,文件结构包含了必要的信息。对于socket来说,使用独立的socket结构保存socket相关的信息,文件结构通过指针来引用socket。socket又引用了tcp_sock。tcp_sock可以分为sock,inet_sock等等,用来支持除了TCP之外的协议,可以认为这是一种多态。
所有TCP协议用到的状态信息都被存在tcp_sock里。例如顺序号、接收窗口、阻塞控制和重发送定时器都保存在tcp_sock中。
socket的发送缓冲区和接收缓冲区由sk_buff链表组成并被包含在tcp_sock中。为防止频繁查找路由,也会在tcp_sock中引用IP路由结果dst_entry。通过dst_entry可以简单的查找到目标的MAC地址之类的ARP的结果。dst_entry是路由表的一部分,而路由表是个很复杂的结构,在这篇文档里就不再讨论了。用来传送数据的网卡(NIC)可以通过dst_entry找到。网卡通过net_device描述。
因此,仅通过查找文件和指针就可以很简单的查找到处理TCP连接的所有的数据结构(从文件到驱动)。这个数据结构的大小就是每个TCP连接占用内存的大小。这个结构占用的内存只有几kb大小(排除了数据包中的数据)。但随着一些功能被加入,内存占用也在逐渐增加。
最后,我们来看一下TCP连接查找表(TCP connection lookup table)。这是一个用来查找接收到的数据包对应tcp连接的哈希表。系统会用数据包的<来源ip,目标ip,来源端口,目标端口>和Jenkins哈希算法去计算哈希值。选择这个哈希函数的原因是为了防止对哈希表的攻击。
4.追踪代码:如何传输数据
我们将会通过追踪实际的Linux内核源码去检查协议栈中执行的关键任务。在这里,我们将会观察经常使用的两条路径。
首先是应用程序调用了write系统调用时的执行路径。
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
|
SYSCALL_DEFINE3
(
write
,
unsigned
int
,
fd
,
const
char
__user *
,
buf
,
.
.
.
)
{
struct
file *
file
;
[
.
.
.
]
file
=
fget_light
(
fd
,
&
fput_needed
)
;
[
.
.
.
]
===
>
ret
=
filp
->
f_op
->
aio_write
(
&
kiocb
,
&
iov
,
1
,
kiocb
.
ki_pos
)
;
struct
file_operations
{
[
.
.
.
]
ssize_t
(
*
aio_read
)
(
struct
kiocb *
,
const
struct
iovec *
,
.
.
.
)
ssize_t
(
*
aio_write
)
(
struct
kiocb *
,
const
struct
iovec *
,
.
.
.
)
[
.
.
.
]
}
;
static
const
struct
file_operations
socket_file_ops
=
{
[
.
.
.
]
.
aio_read
=
sock_aio_read
,
.
aio_write
=
sock_aio_write
,
[
.
.
.
]
}
;
|
当应用调用了write系统调用时,内核将在文件层执行write()函数。首先,内核会取出文件描述符对应的文件结构体,之后会调用aio_write,这是一个函数指针。在文件结构体中,你可以看到file_perations结构体指针。这个结构被通称为函数表(function table),其中包含了一些函数的指针,比如aio_read或者aio_write。对于socket来说,实际的表是socket_file_ops,aio_write对应的函数是sock_aio_write。在这里函数表的作用类似于java中的interface,内核使用这种机制进行代码抽象或重构。
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
|
static
ssize_t
sock_aio_write
(
struct
kiocb *
iocb
,
const
struct
iovec *
iov
,
.
.
)
{
[
.
.
.
]
struct
socket *
sock
=
file
->
private_data
;
[
.
.
.
]
===
>
return
sock
->
ops
->
sendmsg
(
iocb
,
sock
,
msg
,
size
)
;
struct
socket
{
[
.
.
.
]
struct
file *
file
;
struct
sock *
sk
;
const
struct
proto_ops *
ops
;
}
;
const
struct
proto_ops
inet_stream_ops
=
{
.
family
=
PF_INET
,
[
.
.
.
]
.
connect
=
inet_stream_connect
,
.
accept
=
inet_accept
,
.
listen
=
inet_listen
,
.
sendmsg
=
tcp_sendmsg
,
.
recvmsg
=
inet_recvmsg
,
[
.
.
.
]
}
;
struct
proto_ops
{
[
.
.
.
]
int
(
*
connect
)
(
struct
socket *
sock
,
.
.
.
)
int
(
*
accept
)
(
struct
socket *
sock
,
.
.
.
)
int
(
*
listen
)
(
struct
socket *
sock
,
int
len
)
;
int
(
*
sendmsg
)
(
struct
kiocb *
iocb
,
struct
socket *
sock
,
.
.
.
)
int
(
*
recvmsg
)
(
struct
kiocb *
iocb
,
struct
socket *
sock
,
.
.
.
)
[
.
.
.
]
}
;
|
sock_aio_write()函数会从文件结构体中取出socket结构体并调用sendmsg,这也是一个函数指针。socket结构体中包含了proto_ops函数表。IPv4的TCP实现中,proto_ops的具体实现是inet_stream_ops,sendmsg的实现是tcp_sendmsg。
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
|
int
tcp_sendmsg
(
struct
kiocb *
iocb
,
struct
socket *
sock
,
struct
msghdr *
msg
,
size_t
size
)
{
struct
sock *
sk
=
sock
->
sk
;
struct
iovec *
iov
;
struct
tcp_sock *
tp
=
tcp_sk
(
sk
)
;
struct
sk_buff *
skb
;
[
.
.
.
]
mss_now
=
tcp_send_mss
(
sk
,
&
size_goal
,
flags
)
;
/* Ok commence sending. */
iovlen
=
msg
->
msg_iovlen
;
iov
=
msg
->
msg_iov
;
copied
=
0
;
[
.
.
.
]
while
(
--
iovlen
>=
0
)
{
int
seglen
=
iov
->
iov_len
;
unsigned
char
__user *
from
=
iov
->
iov_base
;
iov
++
;
while
(
seglen
>
0
)
{
int
copy
=
0
;
int
max
=
size_goal
;
[
.
.
.
]
skb
=
sk_stream_alloc_skb
(
sk
,
select_size
(
sk
,
sg
)
,
sk
->
sk_allocation
)
;
if
(
!
skb
)
goto
wait_for_memory
;
/*
* Check whether we can use HW checksum.
*/
if
(
sk
->
sk_route_caps
&
NETIF_F_ALL_CSUM
)
skb
->
ip_summed
=
CHECKSUM_PARTIAL
;
[
.
.
.
]
skb_entail
(
sk
,
skb
)
;
[
.
.
.
]
/* Where to copy to? */
if
(
skb_tailroom
(
skb
)
>
0
)
{
/* We have some space in skb head. Superb! */
if
(
copy
>
skb_tailroom
(
skb
)
)
copy
=
skb_tailroom
(
skb
)
;
if
(
(
err
=
skb_add_data
(
skb
,
from
,
copy
)
)
!=
0
)
goto
do_fault
;
[
.
.
.
]
if
(
copied
)
tcp_push
(
sk
,
flags
,
mss_now
,
tp
->
nonagle
)
;
[
.
.
.
]
}
|
tcp_sendmsg(译注:原文写的是tcp_sengmsg,应该是笔误)会从socket中取得tcp_sock(也就是TCP控制块,TCB),并把应用程序请求发送的数据拷贝到socket发送缓冲中(译注:就是根据发送数据创建sk_buff链表)。当把数据拷贝到sk_buff中时,每个sk_buff会包含多少字节数据?在代码创建数据包时,每个sk_buff中会包含MSS字节(通过tcp_send_mss函数获取),在这里MSS表示每个TCP数据包能携带数据的最大值。通过使用TSO(TCP Segment Offload)和GSO(Generic Segmentation Offload)技术,一个sk_buff可以保存大于MSS的数据。在这篇文章里就不详细解释了。
sk_stream_alloc_skb函数会创建新的sk_buff,之后通过skb_entail把新创建的sk_buff放到send_socket_buffer的末尾。skb_add_data函数会把应用层的数据拷贝到sk_buff的buffer中。通过重复这个过程(创建sk_buff然后把它加入到socket发送缓冲区)完成所有数据的拷贝。因此,大小是MSS的多个sk_buff会在socket发送缓冲区中形成一个链表。最终调用tcp_push把待发送的数据做成数据包,并且发送出去。
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
|
static
inline
void
tcp_push
(
struct
sock *
sk
,
int
flags
,
int
mss_now
,
.
.
.
)
[
.
.
.
]
===
>
static
int
tcp_write_xmit
(
struct
sock *
sk
,
unsigned
int
mss_now
,
.
.
.
)
int
nonagle
,
{
struct
tcp_sock *
tp
=
tcp_sk
(
sk
)
;
struct
sk_buff *
skb
;
[
.
.
.
]
while
(
(
skb
=
tcp_send_head
(
sk
)
)
)
{
[
.
.
.
]
cwnd_quota
=
tcp_cwnd_test
(
tp
,
skb
)
;
if
(
!
cwnd_quota
)
break
;
if
(
unlikely
(
!
tcp_snd_wnd_test
(
tp
,
skb
,
mss_now
)
)
)
break
;
[
.
.
.
]
if
(
unlikely
(
tcp_transmit_skb
(
sk
,
skb
,
1
,
gfp
)
)
)
break
;
/* Advance the send_head. This one is sent out.
* This call will increment packets_out.
*/
tcp_event_new_data_sent
(
sk
,
skb
)
;
[
.
.
.
]
|
tcp_push函数会在TCP允许的范围内顺序发送尽可能多的sk_buff数据。首先会调用tcp_send_head取得发送缓冲区中第一个sk_buff,然后调用tcp_cwnd_test和tcp_send_wnd_test检查堵塞窗口和接收窗口,判断接收方是否可以接收新数据。之后调用tcp_transmit_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
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
|
static
int
tcp_transmit_skb
(
struct
sock *
sk
,
struct
sk_buff *
skb
,
int
clone_it
,
gfp_t
gfp_mask
)
{
const
struct
inet_connection_sock *
icsk
=
inet_csk
(
sk
)
;
struct
inet_sock *
inet
;
struct
tcp_sock *
tp
;
[
.
.
.
]
if
(
likely
(
clone_it
)
)
{
if
(
unlikely
(
skb_cloned
(
skb
)
)
)
skb
=
pskb_copy
(
skb
,
gfp_mask
)
;
else
skb
=
skb_clone
(
skb
,
gfp_mask
)
;
if
(
unlikely
(
!
skb
)
)
return
-
ENOBUFS
;
}
[
.
.
.
]
skb_push
(
skb
,
tcp_header_size
)
;
skb_reset_transport_header
(
skb
)
;
skb_set_owner_w
(
skb
,
sk
)
;
/* Build TCP header and checksum it. */
th
=
tcp_hdr
(
skb
)
;
th
->
source
=
inet
->
inet_sport
;
th
->
dest
=
inet
->
inet_dport
;
th
->
seq
=
htonl
(
tcb
->
seq
)
;
th
->
ack_seq
=
htonl
(
tp
->
rcv_nxt
)
;
[
.
.
.
]
icsk
->
icsk_af_ops
->
send_check
(
sk
,
skb
)
;
[
.
.
.
]
err
=
icsk
->
icsk_af_ops
->
queue_xmit
(
skb
)
;
if
(
likely
(
err
<=
0
)
)
return
err
;
tcp_enter_cwr
(
sk
,
1
)
;
return
net_xmit_eval
(
err
)
;
}
|
tcp_transmit_skb会创建指定sk_buff的拷贝(通过pskb_copy),但它不会拷贝应用层发送的数据,而是拷贝一些元数据。之后会调用skb_push来确保和记录头部字段的值。send_check计算TCP校验和(如果使用校验和卸载checksum offload技术则不会做这一步计算)。最终调用queue_xmit把数据发送到IP层。IPv4中queue_xmit的实现函数是ip_queue_xmit。
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
|
int
ip_queue_xmit
(
struct
sk_buff *
skb
)
[
.
.
.
]
rt
=
(
struct
rtable *
)
__sk_dst_check
(
sk
,
0
)
;
[
.
.
.
]
/* OK, we know where to send it, allocate and build IP header. */
skb_push
(
skb
,
sizeof
(
struct
iphdr
)
+
(
opt
?
opt
->
optlen
:
0
)
)
;
skb_reset_network_header
(
skb
)
;
iph
=
ip_hdr
(
skb
)
;
*
(
(
__be16 *
)
iph
)
=
htons
(
(
4
<<
12
)
|
(
5
<<
8
)
|
(
inet
->
tos
&
0xff
)
)
;
if
(
ip_dont_fragment
(
sk
,
&
rt
->
dst
)
&&
!
skb
->
local_df
)
iph
->
frag_off
=
htons
(
IP_DF
)
;
else
iph
->
frag_off
=
0
;
iph
->
ttl
=
ip_select_ttl
(
inet
,
&
rt
->
dst
)
;
iph
->
protocol
=
sk
->
sk_protocol
;
iph
->
saddr
=
rt
->
rt_src
;
iph
->
daddr
=
rt
->
rt_dst
;
[
.
.
.
]
res
=
ip_local_out
(
skb
)
;
[
.
.
.
]
===
>
int
__ip_local_out
(
struct
sk_buff *
skb
)
[
.
.
.
]
ip_send_check
(
iph
)
;
return
nf_hook
(
NFPROTO_IPV4
,
NF_INET_LOCAL_OUT
,
skb
,
NULL
,
skb_dst
(
skb
)
->
dev
,
dst_output
)
;
[
.
.
.
]
===
>
int
ip_output
(
struct
sk_buff *
skb
)
{
struct
net_device *
dev
=
skb_dst
(
skb
)
->
dev
;
[
.
.
.
]
skb
->
dev
=
dev
;
skb
->
protocol
=
htons
(
ETH_P_IP
)
;
return
NF_HOOK_COND
(
NFPROTO_IPV4
,
NF_INET_POST_ROUTING
,
skb
,
NULL
,
dev
,
ip_finish_output
,
[
.
.
.
]
===
>
static
int
ip_finish_output
(
struct
sk_buff *
skb
)
[
.
.
.
]
if
(
skb
->
len
>
ip_skb_dst_mtu
(
skb
)
&&
!
skb_is_gso
(
skb
)
)
return
ip_fragment
(
skb
,
ip_finish_output2
)
;
else
return
ip_finish_output2
(
skb
)
;
|
ip_queue_xmit函数执行IP层的一些必要的任务。__sk_dst_check检查缓存的路由是否有效。如果没有被缓存的路由项,或者路由无效,它将会执行IP路由选择(IP routing)。之后调用skb_push来计算和记录IP头字段的值。之后,随着函数执行,ip_send_check计算IP头校验和并且调用netfilter功能(译注:这是内核的一个模块)。如果使用ip_finish_output函数会创建IP数据分片,但在使用TCP协议时不会创建分片,因此内核会直接调用ip_finish_output2来增加链路头,并完成数据包的创建。
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
|
int
dev_queue_xmit
(
struct
sk_buff *
skb
)
[
.
.
.
]
===
>
static
inline
int
__dev_xmit_skb
(
struct
sk_buff *
skb
,
struct
Qdisc *
q
,
.
.
.
)
[
.
.
.
]
if
(
.
.
.
)
{
.
.
.
.
}
else
if
(
(
q
->
flags
&
TCQ_F_CAN_BYPASS
)
&&
!
qdisc_qlen
(
q
)
&&
qdisc_run_begin
(
q
)
)
{
[
.
.
.
]
if
(
sch_direct_xmit
(
skb
,
q
,
dev
,
txq
,
root_lock
)
)
{
[
.
.
.
]
===
>
int
sch_direct_xmit
(
struct
sk_buff *
skb
,
struct
Qdisc *
q
,
.
.
.
)
[
.
.
.
]
HARD_TX_LOCK
(
dev
,
txq
,
smp_processor_id
(
)
)
;
if
(
!
netif_tx_queue_frozen_or_stopped
(
txq
)
)
ret
=
dev_hard_start_xmit
(
skb
,
dev
,
txq
)
;
HARD_TX_UNLOCK
(
dev
,
txq
)
;
[
.
.
.
]
}
int
dev_hard_start_xmit
(
struct
sk_buff *
skb
,
struct
net_device *
dev
,
.
.
.
)
[
.
.
.
]
if
(
!
list_empty
(
&
ptype_all
)
)
dev_queue_xmit_nit
(
skb
,
dev
)
;
[
.
.
.
]
rc
=
ops
->
ndo_start_xmit
(
skb
,
dev
)
;
[
.
.
.
]
}
|
最终的数据包会通过dev_queue_xmit函数完成传输。首先,数据包通过排队规则(译注:qdisc,上篇文章简单介绍过)传递。如果使用了默认的排队规则并且队列是空的,那么会跳过队列而直接调用sch_direct_xmit把数据包发送到驱动。dev_hard_start_xmit会调用实际的驱动程序。在调用驱动之前,设备的发送(译注:TX,transmit)被锁定,防止多个线程同时使用设备。由于内核锁定了设备的发送,驱动发送数据相关的代码就不需要额外的锁了。这里下次要讲(译注:这里是说原作者的下篇文章)的并行开发有很大关系。
ndo_start_xmit函数会调用驱动的代码。在这之前,你会看到ptype_all和dev_queue_xmit_nit。ptype_all是个包含了一些模块的列表(比如数据包捕获)。如果捕获程序正在运行,数据包会被ptype_all拷贝到其它程序中。因此,tcpdump中显示的都是发送给驱动的数据包。当使用了校验和卸载(checksum offload)或TSO(TCP Segment Offload)这些技术时,网卡(NIC)会操作数据包,所以tcpdump得到的数据包和实际发送到网络的数据包有可能不一致。在结束了数据包传输以后,驱动中断处理程序会返回发送了的sk_buff。
5.追踪代码:如何接收数据
一般来说,接收数据的执行路径是接收一个数据包并把数据加入到socket的接收缓冲区。在执行了驱动中断处理程序之后,首先执行的是napi poll处理程序。
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
|
static
void
net_rx_action
(
struct
softirq_action *
h
)
{
struct
softnet_data *
sd
=
&
__get_cpu_var
(
softnet_data
)
;
unsigned
long
time_limit
=
jiffies
+
2
;
int
budget
=
netdev_budget
;
void
*
have
;
local_irq_disable
(
)
;
while
(
!
list_empty
(
&
sd
->
poll_list
)
)
{
struct
napi_struct *
n
;
[
.
.
.
]
n
=
list_first_entry
(
&
sd
->
poll_list
,
struct
napi_struct
,
poll_list
)
;
if
(
test_bit
(
NAPI_STATE_SCHED
,
&
n
->
state
)
)
{
work
=
n
->
poll
(
n
,
weight
)
;
trace_napi_poll
(
n
)
;
}
[
.
.
.
]
}
int
netif_receive_skb
(
struct
sk_buff *
skb
)
[
.
.
.
]
===
>
static
int
__netif_receive_skb
(
struct
sk_buff *
skb
)
{
struct
packet_type *
ptype
,
*
pt_prev
;
[
.
.
.
]
__be16
type
;
[
.
.
.
]
list_for_each_entry_rcu
(
ptype
,
&
ptype_all
,
list
)
{
if
(
!
ptype
->
dev
||
ptype
->
dev
==
skb
->
dev
)
{
if
(
pt_prev
)
ret
=
deliver_skb
(
skb
,
pt_prev
,
orig_dev
)
;
pt_prev
=
ptype
;
}
}
[
.
.
.
]
type
=
skb
->
protocol
;
list_for_each_entry_rcu
(
ptype
,
&
ptype_base
[
ntohs
(
type
)
&
PTYPE_HASH_MASK
]
,
list
)
{
if
(
ptype
->
type
==
type
&&
(
ptype
->
dev
==
null_or_dev
||
ptype
->
dev
==
skb
->
dev
||
ptype
->
dev
==
orig_dev
)
)
{
if
(
pt_prev
)
ret
=
deliver_skb
(
skb
,
pt_prev
,
orig_dev
)
;
pt_prev
=
ptype
;
}
}
if
(
pt_prev
)
{
ret
=
pt_prev
->
func
(
skb
,
skb
->
dev
,
pt_prev
,
orig_dev
)
;
static
struct
packet_type
ip_packet_type
__read_mostly
=
{
.
type
=
cpu_to_be16
(
ETH_P_IP
)
,
.
func
=
ip_rcv
,
[
.
.
.
]
}
;
|
就像之前说的,net_rx_action函数是用于接收数据的软中断处理函数。首先,请求napi poll的驱动会检索poll_list,并且调用驱动的poll处理程序(poll handler)。驱动会把接收到的数据包包装成sk_buff,之后调用netif_receive_skb。
如果有模块在请求数据包,那么netif_receive_skb会把数据包发送给那个模块。类似于之前讨论过的发送的过程,在这里驱动接收到的数据包会发送给注册到ptype_all列表的那些模块,数据包在这里被捕获。
之后,根据数据包的类型,不同数据包会被传输到相应的上层。链路头中包含了2字节的以太网类型(ethertype)字段,这个字段的值标识了数据包的类型。驱动会把这个值记录到sk_buff中(skb->protocol)。每一种协议有自己的packet_type结构体,并且会把指向结构体的指针放入ptype_base哈希表中。IPv4使用的是ip_packate_type,类型字段中的值是IPv4类型(ETH_P_IP)。于是,对于IPv4类型的数据包会调用ip_recv函数。
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
|
int
ip_rcv
(
struct
sk_buff *
skb
,
struct
net_device *
dev
,
.
.
.
)
{
struct
iphdr *
iph
;
u32
len
;
[
.
.
.
]
iph
=
ip_hdr
(
skb
)
;
[
.
.
.
]
if
(
iph
->
ihl
<
5
||
iph
->
version
!=
4
)
goto
inhdr_error
;
if
(
!
pskb_may_pull
(
skb
,
iph
->
ihl*
4
)
)
goto
inhdr_error
;
iph
=
ip_hdr
(
skb
)
;
if
(
unlikely
(
ip_fast_csum
(
(
u8 *
)
iph
,
iph
->
ihl
)
)
)
goto
inhdr_error
;
len
=
ntohs
(
iph
->
tot_len
)
;
if
(
skb
->
len
<
len
)
{
IP_INC_STATS_BH
(
dev_net
(
dev
)
,
IPSTATS_MIB_INTRUNCATEDPKTS
)
;
goto
drop
;
}
else
if
(
len
<
(
iph
->
ihl*
4
)
)
goto
inhdr_error
;
[
.
.
.
]
return
|