`
simohayha
  • 浏览: 1385555 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

linux下ip协议(V4)的实现(三)

阅读更多
这次我们来看数据包如何从4层传递到3层。

先看下面的图,这张图表示了4层和3层之间(也就是4层传输给3层)的传输所需要调用的主要的函数:




我们注意到3层最终会把帧用dst_output函数进行输出,而这个函数,我们上一次已经讲过了,他会调用skb->dst->output这个虚函数(他会对包进行3层的处理),而最终会调用一个XX_finish_output的函数,从而将数据传递到neighboring子系统。

这张我们主要聚焦于ip_push_pending_frames,ip_append_data,ip_append_page,ip_queue_xmit这几个函数。

ip_queue_xmit:

4层协议(主要指tcp 和 sctp)将数据包按照pmtu切片(如果需要),然后3层的工作只需要给传递下来的切片加上ip头就可以了(也就是说调用这个函数的时候,其实4层已经切好片了)。因此这个函数的处理逻辑比较简单。

ip_push_pending_frames和后面的2个函数:

4层调用这几个函数不会考虑切片,4层调用ip_append_data时会存储请求,也就是会将数据包排队(其中每个都不大于pmtu)到一个输出队列.这样的话使3层的处理更加方便和高效。

当4层需要flush输出队列到3层时,他需要显式的调用ip_push_pending_frames.其实也就是发送包到dst_output.
ip_append_page只是ip_append_data的一个变体。

我们还看到rawip和igmp都是直接调用dst_output,也就是直接和3层交互。

在linux中,每一个bsd socket都被表示为一个socket的数据结构,而每一个protocol family都被表示为一个包含着sock的数据结构,这里我们来看PF_INET的结构:
struct inet_sock {
	/* sk and pinet6 has to be the first two members of inet_sock */
	struct sock		sk;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	struct ipv6_pinfo	*pinet6;
#endif
	/* Socket demultiplex comparisons on incoming packets. */
...................................................
	struct {
..........................................
	} cork;
};


可以看到每个inet_sock都包含一个sock也就是socket,它存储了每个协议簇的私有部分的数据。这样只要给定我们一个sock,我们都能通过inet_sk来得到inet_sock的指针。其实按照他们的内存分布,他们的地址是一样的。

而cork域则在ip_append_data和ip_append_page中扮演的重要的角色,它存储被这两个函数所需要的正确切片的一些上下文信息。

接下来来看ip_queue_xmit的实现,这个函数主要是被tcp和sctp所使用,第一个参数表示被传递的buffer的指针,第二个参数主要是被sctp来使用,就是是否切片被允许的标志:

int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
{
///取出sock,inet_sock以及option
	struct sock *sk = skb->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct ip_options *opt = inet->opt;
	struct rtable *rt;
	struct iphdr *iph;

	/* Skip all of this if the packet is already routed,
	 * f.e. by something like SCTP.
	 */
///得到相关路由信息,如果buffer已经标记了相应的路由信息,则跳过下面的构造路由表。
	rt = skb->rtable;
	if (rt != NULL)
		goto packet_routed;

///下面检测在这个sock中,路由是否已经cache,如果有,则检测这个路由是否还可以使用。
	rt = (struct rtable *)__sk_dst_check(sk, 0);
///cache不存在,查找新路由。
	if (rt == NULL) {
		__be32 daddr;

		/* Use correct destination address if we have options. */
		daddr = inet->daddr;
///检测source route option
		if(opt && opt->srr)
			daddr = opt->faddr;

		{
			struct flowi fl = { .oif = sk->sk_bound_dev_if,
					    .nl_u = { .ip4_u =
						      { .daddr = daddr,
							.saddr = inet->saddr,
							.tos = RT_CONN_FLAGS(sk) } },
					    .proto = sk->sk_protocol,
					    .uli_u = { .ports =
						       { .sport = inet->sport,
							 .dport = inet->dport } } };

			/* If this fails, retransmit mechanism of transport layer will
			 * keep trying until route appears or the connection times
			 * itself out.
			 */
			security_sk_classify_flow(sk, &fl);
///如果是 strict source route option,则会在这个函数中进行下一跳的精确匹配。
			if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
				goto no_route;
		}
///主要是保存一些设备的features。
		sk_setup_caps(sk, &rt->u.dst);
	}

///clone一个skb->dst,也就是引用计数+1了。
	skb->dst = dst_clone(&rt->u.dst);

packet_routed:
///当有strictroute option的时候,检测下一跳,如果不等,则丢掉这个包。这里丢掉包不需要发送icmp,因为我们本身就是源,因此只需要返回错误代码给高层就行了。
	if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
		goto no_route;
///开始build ip头。


///移动指针指向ip头。
	skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
///保存这个指针到network_head
	skb_reset_network_header(skb);
///取出ip头
	iph = ip_hdr(skb);
///实例化ip头。
	*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
	if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)
		iph->frag_off = htons(IP_DF);
	else
		iph->frag_off = 0;
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
	iph->protocol = sk->sk_protocol;
	iph->saddr    = rt->rt_src;
	iph->daddr    = rt->rt_dst;
	/* Transport layer set skb->h.foo itself. */

	if (opt && opt->optlen) {
		iph->ihl += opt->optlen >> 2;
///设定ip头不进行切片。
		ip_options_build(skb, opt, inet->daddr, rt, 0);
	}
///设置ip包的id。
	ip_select_ident_more(iph, &rt->u.dst, sk,
			     (skb_shinfo(skb)->gso_segs ?: 1) - 1);

///用来流量控制。
	skb->priority = sk->sk_priority;
	skb->mark = sk->sk_mark;

///这个函数首先进行ip checksum,最终会通过netfilter的hook,从而由netfilter来决定包丢弃还是传递给dst_output.
	return ip_local_out(skb);

no_route:
	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
	kfree_skb(skb);
	return -EHOSTUNREACH;
}



接下来来看ip_append_data函数,先来看它的参数的含义:

sk: 这个传输包的socket
getfrag: 这个函数用来复制从4层接收到的负荷到数据帧(3层)。
from: 4层的data起始指针。
length: 将要传输的数据的大小,包括4层的头和4层的负荷。
transhdrlen: 四层头的大小
ipc: 需要正确forward数据报的一些信息。
rt: 路由信息
flags:这个变量样子是MSG_XXX,他们包括下面几个定义:

  MSG_MORE: 这个是应用程序用来告诉4层这儿将会有更多的小数据包的传输,然后将这个标记再传递给3层,3层就会提前划分一个mtu大小的数据包,来组合这些数据帧。
  MSG_DONTWAIT: 当这个flag被设置,调用ip_append_data将不会阻塞。
  MSG_PROBE :当这个标记被设置,说明用户不想要真正的传输什么东西,而是知识探测路径。例如测试一个pmtu。

解释下ip+append_data的大体架构,在ip_queue_xmit中,也就是tcp协议使用的传输中,每次传递下来的数据包都要扔给dst_output来处理,而在ip_append_data中,它可以通过MSG_MORE来创建一个最接近mtu大小的数据块,然后将传递下来数据包(小于mtu)的,多个组成一个最接近mtu大小的数据包,然后传递给dst_output.而且他还有一个sk_write_queue队列,这个队列保存了数据传输的请求,也就是将要传递给dst_output的数据包(上面所说的最接近mtu大小的数据包)组成一个队列,从而当ip_push_pending_frams调用时,传递给dst_output.


下面这张图解释了,一个不需要切片,并且包含一个ipsec头的ip包通过ip_append_data后的结果:




这里要注意3层头的填充是通过ip_push_pending来进行填充的。而且一般的4层协议不会直接调用ip_push_pending_frams,而是调用它的包装函数,比如udp就会调用udp_push_pending_frames。

还有一个要注意的是,当没有msg_more时,如果有一个大于pmtu的包传递下来时,他会切包,其中第一个包为pmtu大小,第二个包是剩下的大小,然后把这两个包加入到sk_write_queue队列。而设置了msg_more,此时第二个包的大小就是pmtu,也就是说当再有小的数据包下来,就不需要再次分配空间,而可以直接加入到剩余的数据空间中。

有些硬件设备提供Scatter/Gather I/o这也就意味着能够交由硬件来组合这些小的数据包(3层可以什么都不用做,当数据包离开host的时候,硬件会将它组合好),这样就降低了分配内存和复制数据的开销。

由于一个sk buff只会有一个ip头,因此放到page buff的只会是L4 payload,而不包括头。这里就不需要复制,而是直接将数据放到page buff,接下来的图表示了有Scatter/Gather I/O的情况时,调用ip_append_data之前和之后的区别:


struct skb_frag_struct {
	struct page *page;
	__u32 page_offset;
	__u32 size;
};


这里可以看到nr_frags域来表示有多少个S/G I/O buffer在这个包中被使用。其实整个S/G I/O buffer相当于一个数组,每个元素都是一个skb_frag_t结构,而这个数组的大小就是nr_frags,最大的size是MAX_SKB_FRAGS.

这里要注意,当一个新的帧的大小,大于当前页的剩余大小是,他会被分为两部分,一部分在当前页,一部分在新的页。

没有 s/g I/O:
它会复制数据到当前的data。


4层可以调用ip_append_data多次,在flush这个buff之前。

还有一个getfrag,我再说明下,ip_append_data的任务之一就是复制输入数据到它创建的帧,而不同的协议需要不同的复制操作。比如4层的check sum。有些4层协议就是不需要的。
因此就有了这样一个虚函数,不同的协议实现自己的复制函数,然后传入到ip_append_data.
这个函数其实也就是将用户空间的数据复制到内核空间。
下面这个图就是一些协议实现的复制函数:





接下来的这个图表示了ip_append_data的流程图:




下来我们来看它的具体实现:
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 *rt,
		   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;

///如果只是探测路径则直接返回。
	if (flags&MSG_PROBE)
		return 0;
///当sk_write_queue 为空,意味着创建的是第一个ip帧。因此需要初始化一些相关域。
	if (skb_queue_empty(&sk->sk_write_queue)) {
		/*
		 * setup for corking.
		 */
///初始化cork的一些相关域。
		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;
		}
		dst_hold(&rt->u.dst);
		inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ?
					    rt->u.dst.dev->mtu :
					    dst_mtu(rt->u.dst.path);
		inet->cork.dst = &rt->u.dst;
		inet->cork.length = 0;

		sk->sk_sndmsg_page = NULL;
		sk->sk_sndmsg_off = 0;
		if ((exthdrlen = rt->u.dst.header_len) != 0) {
///加上扩展头和传输层的头的大小。
			length += exthdrlen;
			transhdrlen += exthdrlen;
		}
	} else {
		rt = (struct rtable *)inet->cork.dst;
		if (inet->cork.flags & IPCORK_OPT)
			opt = inet->cork.opt;
///不是第一个帧,则需要把ipsec头和4层的伪头的大小赋值为0.(因为同一个sk,共享相同的头。
		transhdrlen = 0;
		exthdrlen = 0;
		mtu = inet->cork.fragsize;
	}
///得到2层头的大小(也就是预留2层头的大小).
	hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);
///得到3层头的大小。
	fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
///ip包的大小。基于路由pmtu。
	maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;

///由于ip包的最大大小为64kb(oxFFFF),因此拒绝大于这个数据包。
	if (inet->cork.length + length > 0xFFFF - fragheaderlen) {
		ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->dport, mtu-exthdrlen);
		return -EMSGSIZE;
	}

	/*
	 * transhdrlen > 0 means that this is the first fragment and we wish
	 * it won't be fragmented in the future.
	 */
///检测checksum是否需要硬件来做。
	if (transhdrlen &&
	    length + fragheaderlen <= mtu &&
	    rt->u.dst.dev->features & NETIF_F_V4_CSUM &&
	    !exthdrlen)
		csummode = CHECKSUM_PARTIAL;

	inet->cork.length += length;
///检测长度是否大于mtu,以及是否是udp协议。然后进行udp分片。
	if (((length> mtu) || !skb_queue_empty(&sk->sk_write_queue)) &&
	    (sk->sk_protocol == IPPROTO_UDP) &&
	    (rt->u.dst.dev->features & NETIF_F_UFO)) {
///进行udp分片。
		err = ip_ufo_append_data(sk, getfrag, from, length, hh_len,
					 fragheaderlen, transhdrlen, mtu,
					 flags);
		if (err)
			goto error;
		return 0;
	}

	if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL)
		goto alloc_new_skb;

///开始将数据复制到创建的帧。
	while (length > 0) {
		/* Check if the remaining data fits into current packet. */
		copy = mtu - skb->len;

///空间不足时(也就是当前帧剩余的大小不够放入将要复制的数据).
		if (copy < length)
			copy = maxfraglen - skb->len;
///帧太大,需要切片。
		if (copy <= 0) {

			char *data;
			unsigned int datalen;
			unsigned int fraglen;
			unsigned int fraggap;
			unsigned int alloclen;
			struct sk_buff *skb_prev;
alloc_new_skb:
			skb_prev = skb;
///检测上一个skb是否存在
			if (skb_prev)
///存在取得他的fraggap(小于8字节的).这里要解释下fraggap.除了最后一个ip帧,所有的ip帧都必须使他的ip帧的负荷的大小为8字节的倍数。因此当kernel分配一个新的buffer时,他可能需要移动一些数据从前一个buffer的尾部到新的buffer的头部。
				fraggap = skb_prev->len - maxfraglen;
			else
				fraggap = 0;

			/*
			 * If remaining data exceeds the mtu,
			 * we know we need more fragment(s).
			 */
///得到数据长度
			datalen = length + fraggap;
			if (datalen > mtu - fragheaderlen)
				datalen = maxfraglen - fragheaderlen;
			fraglen = datalen + fragheaderlen;
///如果flag为MSG_MORE并且设备设备不支持Scatter/Gather I/O.则需要分配一块等于mtu的内存。
			if ((flags & MSG_MORE) &&
			    !(rt->u.dst.dev->features&NETIF_F_SG))
				alloclen = mtu;
			else
				alloclen = datalen + fragheaderlen;

			/* The last fragment gets additional space at tail.
			 * Note, with MSG_MORE we overallocate on fragments,
			 * because we have no idea what fragment will be
			 * the last.
			 */
			if (datalen == length + fraggap)
				alloclen += rt->u.dst.trailer_len;

///alloc相应的skb。
			if (transhdrlen) {
				skb = sock_alloc_send_skb(sk,
						alloclen + hh_len + 15,
						(flags & MSG_DONTWAIT), &err);
			} else {
				skb = NULL;
				if (atomic_read(&sk->sk_wmem_alloc) <=
				    2 * sk->sk_sndbuf)
					skb = sock_wmalloc(sk,
							   alloclen + hh_len + 15, 1,
							   sk->sk_allocation);
				if (unlikely(skb == NULL))
					err = -ENOBUFS;
			}
///检测是否成功
			if (skb == NULL)
				goto error;
///设置校验位
			skb->ip_summed = csummode;
			skb->csum = 0;
			skb_reserve(skb, hh_len);

///得到数据位置。
			data = skb_put(skb, fraglen);
			skb_set_network_header(skb, exthdrlen);
///得到传输层的头部。
			skb->transport_header = (skb->network_header +
						 fragheaderlen);
			data += fragheaderlen;
///检测是否有fraggap.
			if (fraggap) {
				skb->csum = skb_copy_and_csum_bits(
					skb_prev, maxfraglen,
					data + transhdrlen, fraggap, 0);
				skb_prev->csum = csum_sub(skb_prev->csum,
							  skb->csum);
				data += fraggap;
				pskb_trim_unique(skb_prev, maxfraglen);
			}

///得到所需要拷贝的数据的大小
			copy = datalen - transhdrlen - fraggap;
///开始拷贝数据。
			if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
				err = -EFAULT;
				kfree_skb(skb);
				goto error;
			}

			offset += copy;
			length -= datalen - fraggap;
			transhdrlen = 0;
			exthdrlen = 0;
			csummode = CHECKSUM_NONE;

			/*
			 * Put the packet on the pending queue.
			 */
///加这个包到write_queue队列。
			__skb_queue_tail(&sk->sk_write_queue, skb);
			continue;
		}

		if (copy > length)
			copy = length;

///如果不支持Scatter/Gather I/O.则直接拷贝数据
		if (!(rt->u.dst.dev->features&NETIF_F_SG)) {
			unsigned int off;

			off = skb->len;
			if (getfrag(from, skb_put(skb, copy),
					offset, copy, off, skb) < 0) {
				__skb_trim(skb, off);
				err = -EFAULT;
				goto error;
			}
		} else {
///如果支持S/G I/O则开始进行相应操作
///i为当前已存储的个数。
			int i = skb_shinfo(skb)->nr_frags;
//取出skb_frag_t指针。
			skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];
///得到当前的物理页。
			struct page *page = sk->sk_sndmsg_page;
///得到当前的物理页的位移(也就是我们接下来要存储的位置的位移)
			int off = sk->sk_sndmsg_off;
			unsigned int left;

///如果有足够的空间则将数据放进相应的物理页的位置。
			if (page && (left = PAGE_SIZE - off) > 0) {
///当剩余的空间不够放将要拷贝的数据时,则先将剩余的空间拷贝完毕。然后下次循环再进行拷贝剩下的。
				if (copy >= left)
					copy = left;
				if (page != frag->page) {
					if (i == MAX_SKB_FRAGS) {
						err = -EMSGSIZE;
						goto error;
					}
					get_page(page);
///填充页
					skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);
					frag = &skb_shinfo(skb)->frags[i];
				}
			} 
///检测是否存储空间已满。(此时说明page不存在或者,剩余大小威0,此时需要重新alloc一个物理页。
else if (i < MAX_SKB_FRAGS) {
///检测所需拷贝的数据的大小是否大于页的大小。
				if (copy > PAGE_SIZE)
					copy = PAGE_SIZE;
///则新分配一个页。
				page = alloc_pages(sk->sk_allocation, 0);
				if (page == NULL)  {
					err = -ENOMEM;
					goto error;
				}
				sk->sk_sndmsg_page = page;
				sk->sk_sndmsg_off = 0;

				skb_fill_page_desc(skb, i, page, 0, 0);
				frag = &skb_shinfo(skb)->frags[i];
			} else {
				err = -EMSGSIZE;
				goto error;
			}
///调用getfrag,填充相应的数据包(4层传递下来的数据)
			if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) {
				err = -EFAULT;
				goto error;
			}
			sk->sk_sndmsg_off += copy;
			frag->size += copy;
			skb->len += copy;
			skb->data_len += copy;
			skb->truesize += copy;
			atomic_add(copy, &sk->sk_wmem_alloc);
		}
///计算下次需要再拷贝的。。
		offset += copy;
		length -= copy;
	}

	return 0;

error:
	inet->cork.length -= length;
	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
	return err;
}


在上面的代码中,我们可以看到同一个物理页,有可能被sk_sndmsg_page和skb_frag_t 所共享,可以看下下面的图:





接下来来看ip_append_page,这个函数比较简单,我们大概分析下就可以了。

我们知道内核提供给用户空间的一个零拷贝的接口sendfile.这个接口只能当设备提供Scatter/Gather I/O的时候,才能使用。而它的实现就是基于ip_append_page这个函数来实现的。如果设备不支持S/G I/O,ip_append_page会直接返回错误。

它的逻辑实现和ip_append_page最后面那段实现很相似,不过有些不同,当加一个新的帧到page时,ip_append_page它会merge新的和也在当前页的前一个帧。它会通过调用skb_can_coalesce来进行检测这个。然后当merge是可能的,它就会update前一个帧的长度。
当merge是不可能的时候,处理和ip_append_data相似。
下面就是ip_append_page的一些代码片段:

if (skb_can_coalesce(skb, i, page, offset)) {
			skb_shinfo(skb)->frags[i-1].size += len;
		} else if (i < MAX_SKB_FRAGS) {
			get_page(page);
			skb_fill_page_desc(skb, i, page, offset, len);
		} else {
			err = -EMSGSIZE;
			goto error;
		}


ip_append_page只被udp使用。tcp不使用ip_append_data和ip_push_pending_frams是因为它把一些逻辑放到tcp_sendmsg来实现了。因此相似的,0拷贝接口,tcp不使用ip_append_page是因为他在do_tcp_sendpage中实现了相同的逻辑。

最后我们来看ip_push_pending_frams函数。

这个函数相当于一个notify函数,当4层决定传输帧到ip层的时候,他就需要调用这个函数.通过前面我们知道此时所有的数据(如果不支持Scatter/Gather I/O),都在sk_write_queue中。

这个函数要做的其实很简单,就是从sk_write_queue中取出数据,加上ip头,然后通过dst_output发送给3层。

当数据从sk_write_queue从移除后,加入到frag_list链表中。
下面这张图表示了从sk_write_queue中移除buffer之前和之后的区别(没有考虑Scatter/Gather I/O).





接下来来看它的实现:

int ip_push_pending_frames(struct sock *sk)
{
///初始化一些数据
	struct sk_buff *skb, *tmp_skb;
	struct sk_buff **tail_skb;
	struct inet_sock *inet = inet_sk(sk);
	struct net *net = sock_net(sk);
	struct ip_options *opt = NULL;
	struct rtable *rt = (struct rtable *)inet->cork.dst;
	struct iphdr *iph;
	__be16 df = 0;
	__u8 ttl;
	int err = 0;
///取得第一个buffer
	if ((skb = __skb_dequeue(&sk->sk_write_queue)) == NULL)
		goto out;
///得到他的frag_list.
	tail_skb = &(skb_shinfo(skb)->frag_list);

	/* move skb->data to ip header from ext header */
	if (skb->data < skb_network_header(skb))
		__skb_pull(skb, skb_network_offset(skb));
///开始遍历并取出所有的buffer到frag_list.
	while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
		__skb_pull(tmp_skb, skb_network_header_len(skb));
		*tail_skb = tmp_skb;
		tail_skb = &(tmp_skb->next);
		skb->len += tmp_skb->len;
		skb->data_len += tmp_skb->len;
		skb->truesize += tmp_skb->truesize;
		__sock_put(tmp_skb->sk);
		tmp_skb->destructor = NULL;
		tmp_skb->sk = NULL;
	}

	/* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow
	 * to fragment the frame generated here. No matter, what transforms
	 * how transforms change size of the packet, it will come out.
	 */
	if (inet->pmtudisc < IP_PMTUDISC_DO)
		skb->local_df = 1;

	/* DF bit is set when we want to see DF on outgoing frames.
	 * If local_df is set too, we still allow to fragment this frame
	 * locally. */
	if (inet->pmtudisc >= IP_PMTUDISC_DO ||
	    (skb->len <= dst_mtu(&rt->u.dst) &&
	     ip_dont_fragment(sk, &rt->u.dst)))
///标记ip头不要被切片。
	df = htons(IP_DF);
///如果在头中包含ip option,则给option赋值,然后下面会处理这个option。
	if (inet->cork.flags & IPCORK_OPT)
		opt = inet->cork.opt;
///如果是多播,则赋值多播的ttl
	if (rt->rt_type == RTN_MULTICAST)
		ttl = inet->mc_ttl;
	else
		ttl = ip_select_ttl(inet, &rt->u.dst);

///得到ip头的指针。
	iph = (struct iphdr *)skb->data;
///开始初始化ip头。
	iph->version = 4;
	iph->ihl = 5;
	if (opt) {
		iph->ihl += opt->optlen>>2;
		ip_options_build(skb, opt, inet->cork.addr, rt, 0);
	}
	iph->tos = inet->tos;
	iph->frag_off = df;
///得到ip包的id。
	ip_select_ident(iph, &rt->u.dst, sk);
	iph->ttl = ttl;
	iph->protocol = sk->sk_protocol;
	iph->saddr = rt->rt_src;
	iph->daddr = rt->rt_dst;

	skb->priority = sk->sk_priority;
	skb->mark = sk->sk_mark;
	skb->dst = dst_clone(&rt->u.dst);

///如果协议是ICMP则进行相关处理。
	if (iph->protocol == IPPROTO_ICMP)
		icmp_out_count(net, ((struct icmphdr *)
			skb_transport_header(skb))->type);

	/* Netfilter gets whole the not fragmented skb. */
///输出到4层,这个函数上面有介绍过,会通过一个netfilter的hook.
	err = ip_local_out(skb);
	if (err) {
		if (err > 0)
			err = inet->recverr ? net_xmit_errno(err) : 0;
		if (err)
			goto error;
	}

out:
	ip_cork_release(inet);
	return err;

error:
	IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
	goto out;
}


接下来我们会来简要的介绍4层使用上面的函数接口和3层如何把帧传递给2层的接口:
先来看udp_sendmsg的代码片段:

up->len += ulen;
	getfrag  =  is_udplite ?  udplite_getfrag : ip_generic_getfrag;
///将要传输的包交给ip_append_data来处理
	err = ip_append_data(sk, getfrag, msg->msg_iov, ulen,
			sizeof(struct udphdr), &ipc, rt,
			corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
	if (err)
		udp_flush_pending_frames(sk);
	else if (!corkreq)
///如果需要传递给3层,则调用udp_push_pending_frames,这个函数是对ip_push_pending_frames的简单封装。
		err = udp_push_pending_frames(sk);
	else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
		up->pending = 0;
	release_sock(sk);


我们现在知道4层到3层之后,最终通过dst_output来把帧进行输出,这个函数在单播的情况下,是被实例化为ip_output.这里和前面的netfilter一样,还存在一个ip_output_finish方法,当通过netfilter hook后,如果这个包可以被netfilter放过,那么帧就会传递到ip_output_finish方法,然后再调用ip_output_finish2方法。而最终dev_queue_xmit(前面的blog有介绍,也就是2层的传输方法)会被调用(这里是通过hh->hh_output方法或者fst->neighbour->output 2个虚函数)来传输。


static inline int ip_finish_output2(struct sk_buff *skb)
{
.........................................

	if (dst->hh)
		return neigh_hh_output(dst->hh, skb);
	else if (dst->neighbour)
		return dst->neighbour->output(skb);
...................................
}

  • 大小: 26.8 KB
  • 大小: 24.8 KB
  • 大小: 68.7 KB
  • 大小: 23.7 KB
  • 大小: 55.4 KB
  • 大小: 50.8 KB
  • 大小: 68.8 KB
0
1
分享到:
评论
1 楼 rain2005 2009-07-13  
我也对TCP/IP实现感兴趣啊,可惜看不懂楼主的文章,看英文版电子书太累了,而且时间也不多。

相关推荐

Global site tag (gtag.js) - Google Analytics