作者:gfree.wind@gmail.com
下面开始分析ip_append_data这个函数。可以先看看这个函数都被哪些函数调用,通过搜索,可以发现以下函数都会调用ip_append_data。
1. icmp_push_reply;
2. ip_send_reply;
3. raw_sendmsg;
4. udp_sendmsg;
而ip_send_reply又会被tcp_v4_send_reset和tcp_v4_send_ack调用。可见,ip_append_data基本上只用于udp socket和raw socket。
那为什么正常的TCP发送数据函数不使用这个函数呢。
从ip_append_data的注释上看
- /*
-
* ip_append_data() and ip_append_page() can make one large IP datagram
-
* from many pieces of data. Each pieces will be holded on the socket
-
* until ip_push_pending_frames() is called. Each piece can be a page
-
* or non-page data.
-
*
-
* Not only UDP, other transport protocols - e.g. raw sockets - can use
-
* this interface potentially.
-
*
-
* LATER: length must be adjusted by pad at tail, when it is required.
-
*/
这个函数用于将多个数据,合成为一个大的IP数据包发送过去。——这里我有一个疑问。UDP 报文是有边界的。怎么能轻易的把多个UDP数据,合成一个大的IP报文呢?合成一个后,怎样区分不同的UDP数据包呢?
那么对于TCP的普通数据来说,TCP本身就是一个基于字节流,而非数据报的协议,另外TCP有自己的窗口控制发送数据的速度和大小,那么用这个函数来组成一个大的数据包,对于TCP就没有什么意义了。——这只是我自己的判断,毕竟还没有看到TCP的代码。
那么开始学习代码吧
- int ip_append_data(struct sock *sk,
-
int getfrag(void *from, char *to, int offset, int len,
-
int odd, struct sk_buff *skb),
-
void *from, int length, int transhdrlen,
-
struct ipcm_cookie *ipc, struct rtable **rtp,
-
unsigned int flags)
-
{
-
struct inet_sock *inet = inet_sk(sk);
-
struct sk_buff *skb;
-
-
struct ip_options *opt = NULL;
-
int hh_len;
-
int exthdrlen;
-
int mtu;
-
int copy;
-
int err;
-
int offset = 0;
-
unsigned int maxfraglen, fragheaderlen;
-
int csummode = CHECKSUM_NONE;
-
struct rtable *rt;
-
-
if (flags&MSG_PROBE)
-
return 0;
从今天开始,在学习代码的步骤中,增加对函数参数的说明。
struct sock *sk,这个很简单,就是linu内部的sock结构;getfrag,函数指针,从目前的代码上看,应该是对分片包的处理,例如计算checksum等;from,数据的起始指针;length,数据的长度;
transhdrlen,传输层报文头的长度;ipc,ip包control信息,以option为主;rtp,路由信息;flags,毫无疑问,标志位。
- if (skb_queue_empty(&sk->sk_write_queue)) {
-
/*
-
* setup for corking.
-
*/
-
opt = ipc->opt;
-
if (opt) {
-
if (inet->cork.opt == NULL) {
-
inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation);
-
if (unlikely(inet->cork.opt == NULL))
-
return -ENOBUFS;
-
}
-
memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen);
-
inet->cork.flags |= IPCORK_OPT;
-
inet->cork.addr = ipc->addr;
-
}
-
rt = *rtp;
-
if (unlikely(!rt))
-
return -EFAULT;
-
/*
-
* We steal reference to this route, caller should not release it
-
*/
-
*rtp = NULL;
-
inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ?
-
rt->dst.dev->mtu :
-
dst_mtu(rt->dst.path);
-
inet->cork.dst = &rt->dst;
-
inet->cork.length = 0;
-
sk->sk_sndmsg_page = NULL;
-
sk->sk_sndmsg_off = 0;
-
if ((exthdrlen = rt->dst.header_len) != 0) {
-
length += exthdrlen;
-
transhdrlen += exthdrlen;
-
}
-
}
如果该socket的发送队列为空,那么就需要设置该socket的cork。如果有option信息的话,首先就要复制option信息到socket的cork中,然后设置cork的其他变量,包括分片大小,下一跳地址等。
- if (skb_queue_empty(&sk->sk_write_queue)) {
-
/* skip some codes */
-
} else {
-
rt = (struct rtable *)inet->cork.dst;
-
if (inet->cork.flags & IPCORK_OPT)
-
opt = inet->cork.opt;
-
-
transhdrlen = 0;
-
exthdrlen = 0;
-
mtu = inet->cork.fragsize;
-
}
如果socket的发送队列不为空,那么就直接从cork中取得路由,如果设置了cork的标志位IPCORK_OPT,表明cork中有option,那么就直接引用cork中的option。
看到这里我有一个疑问,没有想清楚。这里的ipc->opt是由udp_sendmsg传下来的,而在udp_sendmsg中,ipc->opt是从用户API中的参数获得的。如果在调用sendto时,该socket的发送缓冲不为空,那么这个opt就不会设置上。岂不是此次操作并没有成功。而且ipc->opt的值也没有被保存,那么此次操作的opt岂不是就丢掉了。望内核高手指教,多谢!