1、基本原理
UDP发包流程中,当没有cork的情况下,会走过udp_sendmsg到达ip_append_data,该接口是IP层提供的UDP和RAW Socket的发包接口,同时,TCP中用于发送ACK和RST报文的接口ip_send_reply最终也会调用此接口
该接口的主要作用是:将数据拷贝到适合的skb(利用发送队列中现有的或新创建)中,可能有两种情况:
1)放入skb的线性区(skb->data)中;
2)或者放入skb_shared_info的分片(frag)中
另外,还需要考虑MTU对skb数据进行分割,为IP层的分片做准备。
2、基本流程
1)如果需要发送的数据长度>MTU,且启用GSO,而且网卡启用UFO,则调用ip_ufo_append_data接口进行处理,默认处理是创建新的page,拷贝数据,并将其链入到skb中的分片中。
2)如果发送队列为空,或者现有的skb中的空余空间(相对于MTU来说)不足以存放本次需要发送的数据,则新创建skb,并将这次需要发送的新数据拷贝拷新创建的skb中。同时,还需要判断原有的skb中的数据是否超过了MTU,如果超过,那么在拷贝新数据之前,还需要将原有skb中超出MTU的部分数据拷贝到新skb中。
3)否则,直接利用现有发送队列中的skb,然后判断网卡硬件是否支持SG特性,如果不支持,则将需要发送的新数据拷贝到现有skb的线性数据区(skb->data)中;如果网卡支持SG特性,则将需要发送的新数据拷贝到SG相关的分片(分散聚集IO页面数组)中。
代码调用流程大致如下:
udp_sendmsg-->
ip_append_data-->
__ip_append_data-->
ip_ufo_append_data //UFO相关处理
sock_alloc_send_skb/sock_wmalloc //分配新skb
getfrag //拷贝发送数据到skb中
3、代码分析
ip_append_data():
1. 主要把用户空间的数据,以skb的方式组织
2. * 主要用作UDP和Raw socket的输出接口,但TCP中用于发送ACK和RST的函数ip_send_reply()最终也调用了此接口。
3. * 主要作用是将数据拷贝到适合的skb(利用发送队列中现有的或新创建)中,可能有两种情况: 放入skb的线性
4. * 区(skb->data)中,或者放入skb_shared_info的分片(frag)中,同时还需要考虑MTU对skb数据进行分割,为IP层的分片做准备。
5.
6. int ip_append_data(struct sock *sk, struct flowi4 *fl4,
7. int getfrag(void *from, char *to, int offset, int len,
8. int odd, struct sk_buff *skb),
9. void *from, int length, int transhdrlen,
10. struct ipcm_cookie *ipc, struct rtable **rtp,
11. unsigned int flags)
12. {
13. struct inet_sock *inet = inet_sk(sk);
14. int err;
15. /*MSG_PROBE标记表示探测,并非要发送实际数据*/
16. if (flags&MSG_PROBE)
17. return 0;
18. /*如果传输控制块(sock)的的输出队列为空,则需要设置一些临时信息,如果不为空,那就可以使用上次发送时的相关信息*/
19. if (skb_queue_empty(&sk->sk_write_queue)) {
20. err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
21. if (err)
22. return err;
23. } else {
24. transhdrlen = 0;
25. }
26.
27. return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
28. sk_page_frag(sk), getfrag,
29. from, length, transhdrlen, flags);
30. }
ip_append_data()-->__ip_append_data():
1. static int __ip_append_data(struct sock *sk,
2. struct flowi4 *fl4,
3. struct sk_buff_head *queue,
4. struct inet_cork *cork,
5. struct page_frag *pfrag,
6. int getfrag(void *from, char *to, int offset,
7. int len, int odd, struct sk_buff *skb),
8. void *from, int length, int transhdrlen,
9. unsigned int flags)
10. {
11. struct inet_sock *inet = inet_sk(sk);
12. struct sk_buff *skb;
13.
14. struct ip_options *opt = cork->opt;
15. int hh_len;
16. /*用于记录IPsec中扩展首部的长度,未启用IPsec是为0*/
17. int exthdrlen;
18. int mtu;
19. int copy;
20. int err;
21. int offset = 0;
22. unsigned int maxfraglen, fragheaderlen;
23. int csummode = CHECKSUM_NONE;
24. struct rtable *rt = (struct rtable *)cork->dst;
25.
26. skb = skb_peek_tail(queue);
27.
28. exthdrlen = !skb ? rt->dst.header_len : 0;
29. mtu = cork->fragsize;
30. /*获取链路层首部的长度*/
31. hh_len = LL_RESERVED_SPACE(rt->dst.dev);
32. /*获取IP首部(包括IP选项)的长度*/
33. fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
34. /*IP数据包中数据的最大长度,通过mtu计算,并进行8字节对齐,目的是提升计算效率*/
35. maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
36. /*输出的报文长度不能超过IP数据报能容纳的最大长度(64K)*/
37. if (cork->length + length > 0xFFFF - fragheaderlen) {
38. ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
39. mtu-exthdrlen);
40. return -EMSGSIZE;
41. }
42.
43. /*
44. * transhdrlen > 0 means that this is the first fragment and we wish
45. * it won't be fragmented in the future.
46. */
47. /*如果没有分片,且硬件支持计算校验和,则设置CHECKSUM_PARTIAL标记,由硬件来计算校验和*/
48. if (transhdrlen &&
49. length + fragheaderlen <= mtu &&
50. rt->dst.dev->features & NETIF_F_V4_CSUM &&
51. !exthdrlen)
52. csummode = CHECKSUM_PARTIAL;
53. /*增加cork阻塞的长度*/
54. cork->length += length;
55. if (((length > mtu) || (skb && skb_is_gso(skb))) &&
56. (sk->sk_protocol == IPPROTO_UDP) &&
57. (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len) {
58. /*
59. * UFO处理,需要满足上述几个条件,主要为:数据长度>mtu + 启用GSO + 网卡启用UFO.
60. * 默认处理是创建新的page,拷贝数据,并将其链入到skb中的分片中(skb_shared_info,SG相关)
61. */
62. err = ip_ufo_append_data(sk, queue, getfrag, from, length,
63. hh_len, fragheaderlen, transhdrlen,
64. maxfraglen, flags);
65. if (err)
66. goto error;
67. return 0;
68. }
69.
70. /* So, what's going on in the loop below?
71. *
72. * We use calculated fragment length to generate chained skb,
73. * each of segments is IP fragment ready for sending to network after
74. * adding appropriate IP header.
75. */
76. /*如果输出队列中没有skb,那说明队列为空,需要新创建skb来发送数据*/
77. if (!skb)
78. goto alloc_new_skb;
79.
80. while (length > 0) {
81. /* Check if the remaining data fits into current packet. */
82. /*计算当前skb中还能放多少数据,通过mtu-skb的数据长度计算*/
83. copy = mtu - skb->len;
84. /*
85. * 如果skb的剩余空间不足以存放完这次需要放入的数据长度length,则将当前skb填满即可,剩余数据留下一个skb发送
86. * 其中maxfraglen是8字节对齐后的mtu。
87. */
88. if (copy < length)
89. copy = maxfraglen - skb->len;
90. /*如果当前skb中的数据本身就已经大于MTU了,那就需要新分配skb来容纳新数据了*/
91. if (copy <= 0) {
92. char *data;
93. unsigned int datalen;
94. unsigned int fraglen;
95. unsigned int fraggap;
96. unsigned int alloclen;
97. struct sk_buff *skb_prev;
98. alloc_new_skb:/*两种原因需要新分配skb: 1.原有的skb数据区空间不足2.sock的输出队列为空*/
99. skb_prev = skb;
100. /*由于原有的skb数据区空间不足,而需要分配新skb,计算不足的大小*/
101. if (skb_prev)
102. fraggap = skb_prev->len - maxfraglen;
103. /*由于sock的输出队列为空*/
104. else
105. fraggap = 0;
106.
107. /*
108. * If remaining data exceeds the mtu,
109. * we know we need more fragment(s).
110. */
111. /*这次需要新分配的数据区大小length加上原来skb中不足的大小,为新skb需要分配的数据区大小*/
112. datalen = length + fraggap;
113. /*如果新skb需要分配的数据区大小超过了mtu,那这次还是只能分配mtu的大小,剩余数据需要通过循环分配新skb来容纳*/
114. if (datalen > mtu - fragheaderlen)
115. datalen = maxfraglen - fragheaderlen;
116. /*数据报分片大小需要加上IP首部的长度*/
117. fraglen = datalen + fragheaderlen;
118. /*如果设置了MSG_MORE标记,表明需要等待新数据,一直到超过mtu为止,则设置"分配空间大小"为mtu*/
119. if ((flags & MSG_MORE) &&
120. !(rt->dst.dev->features&NETIF_F_SG))
121. alloclen = mtu;
122. /*否则需要分配的空间大小为数据报分片大小*/
123. else
124. alloclen = fraglen;
125. /*再加上IPsec相关的头部长度*/
126. alloclen += exthdrlen;
127.
128. /* The last fragment gets additional space at tail.
129. * Note, with MSG_MORE we overallocate on fragments,
130. * because we have no idea what fragment will be
131. * the last.
132. */
133. if (datalen == length + fraggap)
134. alloclen += rt->dst.trailer_len;
135. /*
136. * 根据是否存在传输层首部,确定分配skb的方法:
137. * 如果存在,则说明该分片为分片组中的第一个分片,那就需要考虑更多的情况,比如:发送是否超时、是否发生未处理的致命错误、
138. * 发送通道是否已经关闭等;当不存在传输层首部时,说明不是第一个分片,则不需考虑这些情况。
139. */
140. if (transhdrlen) {
141. /*分配skb,并进行相关处理*/
142. skb = sock_alloc_send_skb(sk,
143. alloclen + hh_len + 15,
144. (flags & MSG_DONTWAIT), &err);
145. } else {
146. skb = NULL;
147. if (atomic_read(&sk->sk_wmem_alloc) <=
148. 2 * sk->sk_sndbuf)
149. /*分配skb*/
150. skb = sock_wmalloc(sk,
151. alloclen + hh_len + 15, 1,
152. sk->sk_allocation);
153. if (unlikely(skb == NULL))
154. err = -ENOBUFS;
155. else
156. /* only the initial fragment is
157. time stamped */
158. cork->tx_flags = 0;
159. }
160. if (skb == NULL)
161. goto error;
162.
163. /*
164. * Fill in the control structures
165. */
166. /*初始化skb中的相关成员*/
167. skb->ip_summed = csummode;
168. skb->csum = 0;
169. skb_reserve(skb, hh_len);
170. skb_shinfo(skb)->tx_flags = cork->tx_flags;
171.
172. /*
173. * Find where to start putting bytes.
174. */
175. /*在skb中预留存放二层首部、三层首部和数据的空间*/
176. data = skb_put(skb, fraglen + exthdrlen);
177. /*设置IP头指针位置*/
178. skb_set_network_header(skb, exthdrlen);
179. /*计算传输层头部长度*/
180. skb->transport_header = (skb->network_header +
181. fragheaderlen);
182. /*计算数据存入的位置*/
183. data += fragheaderlen + exthdrlen;
184. /*
185. * 如果上一个skb的数据大于mtu(8字节对齐),那么需要将上一个skb中超出的数据和传输层首部
186. * 复制到当前的skb中,并重新计算校验和。
187. */
188. if (fraggap) {
189. skb->csum = skb_copy_and_csum_bits(
190. skb_prev, maxfraglen,
191. data + transhdrlen, fraggap, 0);
192. /*上一个skb的校验和也需要重新计算*/
193. skb_prev->csum = csum_sub(skb_prev->csum,
194. skb->csum);
195. /*拷贝新数据后,再次移动data指针,更新数据写入的位置*/
196. data += fraggap;
197. /*已8字节对齐的MTU大小截取上一个skb,多余的数据已经拷贝到新的skb中了,需要截掉*/
198. pskb_trim_unique(skb_prev, maxfraglen);
199. }
200. /*拷贝新数据到数据区*/
201. copy = datalen - transhdrlen - fraggap;
202. if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
203. err = -EFAULT;
204. kfree_skb(skb);
205. goto error;
206. }
207. /*更新下次拷贝相关的数据*/
208. offset += copy;
209. length -= datalen - fraggap;
210. /*由于传输层首部已经拷贝过了,所以相关变量置0*/
211. transhdrlen = 0;
212. exthdrlen = 0;
213. csummode = CHECKSUM_NONE;
214.
215. /*
216. * Put the packet on the pending queue.
217. */
218. /*复制完数据的skb添加到sock输出队列的末尾*/
219. __skb_queue_tail(queue, skb);
220. continue;
221. }
222. /*如果当前skb中的剩余数据区大小(相对于mtu),那说明这次要发送的数据可以直接放入当前的skb中,直接拷贝即可。*/
223. if (copy > length)
224. copy = length;
225. /*如果硬件不支持SG(分散聚集特性,使用skb中的非线性区(shared_info))*/
226. if (!(rt->dst.dev->features&NETIF_F_SG)) {
227. unsigned int off;
228.
229. off = skb->len;
230. /*将数据拷贝到skb中的线性区*/
231. if (getfrag(from, skb_put(skb, copy),
232. offset, copy, off, skb) < 0) {
233. __skb_trim(skb, off);
234. err = -EFAULT;
235. goto error;
236. }
237. } else {/*如果硬件支持SG,则将数据拷贝到skb的非线性区(shared_info)中*/
238. int i = skb_shinfo(skb)->nr_frags;
239.
240. err = -ENOMEM;
241. /*在分片列表(frags)中使用原有分片(返回相应分片的指针)或分配新页来存放数据*/
242. if (!sk_page_frag_refill(sk, pfrag))
243. goto error;
244. /*
245. * 如果传输控制块(sock)中的缓存页pfrag,不是当前skb->shared_info中的最后一个分片(分散聚集IO页面)所在的页面,则直接使用该页面,
246. * 将其添加 到分片列表(分散聚集IO页面数组)中,否则说明传输控制块(sock)中的缓存页pfrag就是分散聚集IO页面的最后一个页面,
247. * 则直接向其中拷贝数据即可。
248. */
249. if (!skb_can_coalesce(skb, i, pfrag->page,
250. pfrag->offset)) {
251. err = -EMSGSIZE;
252. if (i == MAX_SKB_FRAGS)
253. goto error;
254.
255. __skb_fill_page_desc(skb, i, pfrag->page,
256. pfrag->offset, 0);
257. skb_shinfo(skb)->nr_frags = ++i;
258. get_page(pfrag->page);
259. }
260. copy = min_t(int, copy, pfrag->size - pfrag->offset);
261. /*拷贝数据至skb中非线性区分片(分散聚集IO页面)中*/
262. if (getfrag(from,
263. page_address(pfrag->page) + pfrag->offset,
264. offset, copy, skb->len, skb) < 0)
265. goto error_efault;
266. /*移动相应数据指针*/
267. pfrag->offset += copy;
268. /*增加分片大小*/
269. skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
270. /*增加skb数据相关大小*/
271. skb->len += copy;
272. skb->data_len += copy;
273. skb->truesize += copy;
274. /*增加sock发送缓存区已分配数据大小*/
275. atomic_add(copy, &sk->sk_wmem_alloc);
276. }
277. offset += copy;
278. /*length减去已经拷贝的大小,如果拷完了,则结束循环,否则继续拷贝*/
279. length -= copy;
280. }
281.
282. return 0;
283.
284. error_efault:
285. err = -EFAULT;
286. error:
287. cork->length -= length;
288. IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
289. return err;
290. }