ip_append_data分析

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.         }

猜你喜欢

转载自blog.csdn.net/bin_linux96/article/details/79421323