TCP实现之:L3的IP协议

TCP实现之:L3的IP协议

1. 报文的接收

从上一篇文章TCP实现之:Hellow World我们已经知道了,网卡驱动(也可以理解为L2层)是通过IP协议层注册的ip_packet_type来讲数据传递给IP层处理的,其处理函数为ip_rcv,下面我们来重点分析一下该函数。

ip_rcv

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
	   struct net_device *orig_dev)
{
	struct net *net = dev_net(dev);

    /* skb主要的校验函数,包括丢弃非发往本机的数据包(混杂模式)、IP报头校验等 */
	skb = ip_rcv_core(skb, net);
	if (skb == NULL)
		return NET_RX_DROP;

    /* 调用防火墙的钩子,在通过防火墙规则后将报文传递给ip_rcv_finish函数 */
	return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
		       net, NULL, skb, dev, NULL,
		       ip_rcv_finish);
}

ip_rcv_core

static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net)
{
	const struct iphdr *iph;
	u32 len;

	/* 网卡混杂模式,丢弃非本机的数据报。此时该本文在L2已经经历了报文嗅探器,
	 * 没啥用了,可以丢弃。 
	 */
	if (skb->pkt_type == PACKET_OTHERHOST)
		goto drop;

	/* 信息统计 */
	__IP_UPD_PO_STATS(net, IPSTATS_MIB_IN, skb->len);

	/* 检测skb的引用计数。skb可能被其他地方使用,如L2层的嗅探器等。如果被其他
	 * 地方使用,即引用计数>1,则创建一份skb的副本来使用 
	 */
	skb = skb_share_check(skb, GFP_ATOMIC);
	if (!skb) {
		__IP_INC_STATS(net, IPSTATS_MIB_INDISCARDS);
		goto out;
	}

	/* IP头部完整性校验 */
	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
		goto inhdr_error;

	iph = ip_hdr(skb);

	/* 丢弃头部不完整或者非IPv4版本的数据报 */
	if (iph->ihl < 5 || iph->version != 4)
		goto inhdr_error;

	BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1);
	BUILD_BUG_ON(IPSTATS_MIB_ECT0PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_0);
	BUILD_BUG_ON(IPSTATS_MIB_CEPKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_CE);
	__IP_ADD_STATS(net,
		       IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK),
		       max_t(unsigned short, 1, skb_shinfo(skb)->gso_segs));

	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 csum_error;

	len = ntohs(iph->tot_len);
	if (skb->len < len) {
		__IP_INC_STATS(net, IPSTATS_MIB_INTRUNCATEDPKTS);
		goto drop;
	} else if (len < (iph->ihl*4))
		goto inhdr_error;

	/* Our transport medium may have padded the buffer out. Now we know it
	 * is IP we can trim to the true length of the frame.
	 * Note this now means skb->len holds ntohs(iph->tot_len).
	 */
	if (pskb_trim_rcsum(skb, len)) {
		__IP_INC_STATS(net, IPSTATS_MIB_INDISCARDS);
		goto drop;
	}

	iph = ip_hdr(skb);
	skb->transport_header = skb->network_header + iph->ihl*4;

	/* Remove any debris in the socket control block */
	memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
	IPCB(skb)->iif = skb->skb_iif;

	/* Must drop socket now because of tproxy. */
	skb_orphan(skb);

	return skb;

csum_error:
	__IP_INC_STATS(net, IPSTATS_MIB_CSUMERRORS);
inhdr_error:
	__IP_INC_STATS(net, IPSTATS_MIB_INHDRERRORS);
drop:
	kfree_skb(skb);
out:
	return NULL;
}

ip_rcv_finish

下面我们来分析一下真正的IP报文接收处理函数-ip_rcv_finish。这个函数会先调用ip_rcv_finish_core函数对skb做一些初始化的工作,然后调用dst_input来根据skb的路由对数据报进行进一步的处理。

static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	struct net_device *dev = skb->dev;
	int ret;

	/* if ingress device is enslaved to an L3 master device pass the
	 * skb to its handler for processing
	 */
	skb = l3mdev_ip_rcv(skb);
	if (!skb)
		return NET_RX_SUCCESS;

	ret = ip_rcv_finish_core(net, sk, skb, dev);
	if (ret != NET_RX_DROP)
		调用
		ret = dst_input(skb);
	return ret;
}

ip_rcv_finish_core

ip_rcv_finish_core函数所做的工作主要包括:(1)判断是否需要提前分流;(2)初始化skb路由;(3)处理IP选项。

static int ip_rcv_finish_core(struct net *net, struct sock *sk,
			      struct sk_buff *skb, struct net_device *dev)
{
	const struct iphdr *iph = ip_hdr(skb);
	int (*edemux)(struct sk_buff *skb);
	struct rtable *rt;
	int err;

	/* 提前分流 */
	if (net->ipv4.sysctl_ip_early_demux &&
	    !skb_dst(skb) &&
	    !skb->sk &&
	    !ip_is_fragment(iph)) {
		const struct net_protocol *ipprot;
		/* IP的上层协议 */
		int protocol = iph->protocol;

		ipprot = rcu_dereference(inet_protos[protocol]);
		if (ipprot && (edemux = READ_ONCE(ipprot->early_demux))) {
			/* 调用上层协议的提前分流函数,如tcp_v4_early_demux */
			err = edemux(skb);
			if (unlikely(err))
				goto drop_error;
			/* 重新加载IP头部 */
			iph = ip_hdr(skb);
		}
	}

	/* 
	 * 初始化skb的路由缓存。这决定了skb在协议栈中的处理方式,是deliver到上层协议
	 * 还是进行ip_forward转发等。
	 * /
	if (!skb_valid_dst(skb)) {
		err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
					   iph->tos, dev);
		if (unlikely(err))
			goto drop_error;
	}

#ifdef CONFIG_IP_ROUTE_CLASSID
	if (unlikely(skb_dst(skb)->tclassid)) {
		struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
		u32 idx = skb_dst(skb)->tclassid;
		st[idx&0xFF].o_packets++;
		st[idx&0xFF].o_bytes += skb->len;
		st[(idx>>16)&0xFF].i_packets++;
		st[(idx>>16)&0xFF].i_bytes += skb->len;
	}
#endif

	/* 处理IP选项。IP选项不像TCP选项那样,很少被用到 */
	if (iph->ihl > 5 && ip_rcv_options(skb))
		goto drop;

	/* 找到skb的路由表 */
	rt = skb_rtable(skb);
	if (rt->rt_type == RTN_MULTICAST) {
		__IP_UPD_PO_STATS(net, IPSTATS_MIB_INMCAST, skb->len);
	} else if (rt->rt_type == RTN_BROADCAST) {
		__IP_UPD_PO_STATS(net, IPSTATS_MIB_INBCAST, skb->len);
	} else if (skb->pkt_type == PACKET_BROADCAST ||
		   skb->pkt_type == PACKET_MULTICAST) {
		struct in_device *in_dev = __in_dev_get_rcu(dev);

		if (in_dev &&
		    IN_DEV_ORCONF(in_dev, DROP_UNICAST_IN_L2_MULTICAST))
			goto drop;
	}

	return NET_RX_SUCCESS;

drop:
	kfree_skb(skb);
	return NET_RX_DROP;

drop_error:
	if (err == -EXDEV)
		__NET_INC_STATS(net, LINUX_MIB_IPRPFILTER);
	goto drop;
}

ip_local_deliver

dst_input(skb)函数会调用skb路由选项的input函数,该函数在初始化路由缓存时确定,可能为ip_local_deliver或者ip_forward。这里我们先分析一下ip_local_deliver函数,该函数顾名思义,就是将skb交给上层协议的。

int ip_local_deliver(struct sk_buff *skb)
{	
	struct net *net = dev_net(skb->dev);

	/* 当skb为IP分片时,对其进行重组 */
	if (ip_is_fragment(ip_hdr(skb))) {
		if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
			return 0;
	}

	/* 调用防火墙的钩子,并在通过验证后调用ip_local_deliver_finish */
	return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
		       net, NULL, skb, skb->dev, NULL,
		       ip_local_deliver_finish);
}

ip_local_deliver_finish

static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	__skb_pull(skb, skb_network_header_len(skb));

	rcu_read_lock();
	{
		/* 获取L4协议 */
		int protocol = ip_hdr(skb)->protocol;
		const struct net_protocol *ipprot;
		int raw;

	resubmit:
		/* 处理原始套接字(即SOCK_RAW) */
		raw = raw_local_deliver(skb, protocol);

		ipprot = rcu_dereference(inet_protos[protocol]);
		if (ipprot) {
			/* 
			 * 这里我们可以看到,即使使用的是SOCK_RAW,在将报文交给套接字处理函数
			 * 处理后,还是会将报文继续向上层协议传递。
			 */
			int ret;

			if (!ipprot->no_policy) {
				if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					kfree_skb(skb);
					goto out;
				}
				nf_reset(skb);
			}
			/* 调用L4的skb处理函数 */
			ret = ipprot->handler(skb);
			if (ret < 0) {
				protocol = -ret;
				goto resubmit;
			}
			__IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
		} else {
			if (!raw) {
				/* 找不到处理协议,向发送方发送一条ICMP消息,告诉它一声 */
				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					__IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
					icmp_send(skb, ICMP_DEST_UNREACH,
						  ICMP_PROT_UNREACH, 0);
				}
				kfree_skb(skb);
			} else {
				__IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
				consume_skb(skb);
			}
		}
	}
 out:
	rcu_read_unlock();

	return 0;
}

从上面可以看出,在将报文向上层协议传递时,它会找到L4协议注册到IP层的struct net_protocol,注册函数为inet_add_protocol。以TCP协议为例,其定义为:

static struct net_protocol tcp_protocol = {
	.early_demux	=	tcp_v4_early_demux,
	.early_demux_handler =  tcp_v4_early_demux,
	.handler	=	tcp_v4_rcv,
	.err_handler	=	tcp_v4_err,
	.no_policy	=	1,
	.netns_ok	=	1,
	.icmp_strict_tag_validation = 1,
};

参考链接:【linux网络】ip_rcv()函数Linux内核IP层的报文处理流程

2. 报文的发送

__ip_make_skb

首先我们来讲一下__ip_make_skb这个函数。在进行报文发送时,一般待发送的报文都会放在套接字的sk_write_queue发送队列等待发送。而__ip_make_skb函数的作用正是将队列中所有的IP片段合并成一个IP数据报,然后将其从队列中取出以进行发送。

struct sk_buff *__ip_make_skb(struct sock *sk,
			      struct flowi4 *fl4,
			      struct sk_buff_head *queue,
			      struct inet_cork *cork)
{
	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 *)cork->dst;
	struct iphdr *iph;
	__be16 df = 0;
	__u8 ttl;

	/* 取出队列中的一个skb */
	skb = __skb_dequeue(queue);
	if (!skb)
		goto out;
	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));

	/* 下面的循环将queue队列中的skb一个个地取出,并加到skb的frag_list链表的尾部 */
	while ((tmp_skb = __skb_dequeue(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;
		tmp_skb->destructor = NULL;
		tmp_skb->sk = NULL;
	}

	......
	/*
	 * Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec
	 * on dst refcount
	 */
	cork->dst = NULL;
	skb_dst_set(skb, &rt->dst);

	if (iph->protocol == IPPROTO_ICMP)
		icmp_out_count(net, ((struct icmphdr *)
			skb_transport_header(skb))->type);

	ip_cork_release(cork);
out:
	return skb;
}

不同的上层协议调用的IP层处理函数也不同,下面我们以UDP协议为例来分析一下UDP报文在IP层的处理逻辑。UDP协议报文的发送函数为udp_send_skb,该函数会调用ip_send_skb来将报文交给IP层进行处理,下面我们就从这个函数来展开讨论。

ip_send_skb

ip_send_skb函数的定义如下:

int ip_send_skb(struct net *net, struct sk_buff *skb)
{
	int err;

	err = ip_local_out(net, skb->sk, skb);
	if (err) {
		if (err > 0)
			err = net_xmit_errno(err);
		if (err)
			IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
	}

	return err;
}

从上面我们可以看出该函数直接调用了ip_local_out来进行报文的发送,并调用net_xmit_errno对结果进行了处理,该函数用于将错误代码转换为上层协议可以理解的代码。下面我们来看看ip_local_out函数的处理过程。

ip_local_out

int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	int err;

	err = __ip_local_out(net, sk, skb);
	if (likely(err == 1))
		err = dst_output(net, sk, skb);

	return err;
}

从上面的代码中我们可以看出该函数直接调用了__ip_local_out函数,并在返回值为1的时候将其交给路由层进行处理。至于为什么这样,我们先不管,我们先看看__ip_local_out的处理逻辑。

__ip_local_out

int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	struct iphdr *iph = ip_hdr(skb);

	iph->tot_len = htons(skb->len);
	ip_send_check(iph);

	/* if egress device is enslaved to an L3 master device pass the
	 * skb to its handler for processing
	 */
	/* 新加内容,还不知道干啥的 */
	skb = l3mdev_ip_out(sk, skb);
	if (unlikely(!skb))
		return 0;

	/* 将报文协议设置为IP协议 */
	skb->protocol = htons(ETH_P_IP);

	return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
		       net, sk, skb, NULL, skb_dst(skb)->dev,
		       dst_output);
}

这个函数主要做了三件事:

  1. 设置IP报头里的数据长度;
  2. 调用ip_send_check来计算报文的校验和,这个函数会调用ip_fast_csum来对校验和进行计算;
  3. 将报文交给防火墙钩子,并在通过后调用dst_output函数。

dst_output

dst_output属于路由模块的功能,这个函数的定义很简单,它会直接调用skbdst实体的output方法,根据dst实体的不同,不同的处理函数会被调用,从而使得报文的处理变得更加灵活。在UDP报文发送过程中,udp_sendmsg函数调用了ip_route_output_flow函数,该函数又调用了__ip_route_output_key函数,最后调用了__mkroute_output函数。这个函数会创建skb的路由,并根据路由的不同来创建不同的dst实体。据说,在大多数情况下这个实体的函数都是ip_output

static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	return skb_dst(skb)->output(net, sk, skb);
}

ip_output

进到这个函数里面,报文离发出去就不远啦!这个函数首先根据skb的路由将skb的网络设备设置为出口设备;然后它将报文协议设为IP协议,并将其交给防火墙的钩子,在通过防火墙后交给ip_finish_output处理(这个流程怎么这么眼熟呢)。

int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	struct net_device *dev = skb_dst(skb)->dev;

	IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT, skb->len);

	skb->dev = dev;
	skb->protocol = htons(ETH_P_IP);

	return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,
			    net, sk, skb, NULL, dev,
			    ip_finish_output,
			    !(IPCB(skb)->flags & IPSKB_REROUTED));
}

ip_finish_output

这个函数可谓是IP层的一个关键函数,它所做的工作主要有:

  1. BPF_CGROUP_RUN_PROG_INET_EGRESS:还不知道这个是干嘛的;
  2. 检查skb的路由是否是xfrm,xfrm是IPSEC的一个实现,是一种在不打乱原有协议栈的基础上实现的一种基于策略的高扩展性网络安全架构,这里不再展开讨论;
  3. 检查skb是否是gso报文,由于gso报文不在IP层进行分片,因此它不能走常规路线,而是调用了ip_finish_output_gso函数;
  4. skb长度大于mtu,则需要进行分片,从而交给ip_fragment,这个函数会对报文进行分片,并在完成后调用ip_finish_output2函数;
  5. 代用ip_finish_output2处理报文。
static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	unsigned int mtu;
	int ret;

	ret = BPF_CGROUP_RUN_PROG_INET_EGRESS(sk, skb);
	if (ret) {
		kfree_skb(skb);
		return ret;
	}

#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
	/* Policy lookup after SNAT yielded a new policy */
	if (skb_dst(skb)->xfrm) {
		IPCB(skb)->flags |= IPSKB_REROUTED;
		return dst_output(net, sk, skb);
	}
#endif
	mtu = ip_skb_dst_mtu(sk, skb);
	if (skb_is_gso(skb))
		return ip_finish_output_gso(net, sk, skb, mtu);

	if (skb->len > mtu || (IPCB(skb)->flags & IPSKB_FRAG_PMTU))
		return ip_fragment(net, sk, skb, mtu, ip_finish_output2);

	return ip_finish_output2(net, sk, skb);
}

ip_finish_output2

下面我们来看看ip_finish_output2这个函数,这个函数首先检查报文是否是多播或组播,是的话就把对应的计数器更新。

static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	struct dst_entry *dst = skb_dst(skb);
	struct rtable *rt = (struct rtable *)dst;
	struct net_device *dev = dst->dev;
	unsigned int hh_len = LL_RESERVED_SPACE(dev);
	struct neighbour *neigh;
	u32 nexthop;

	if (rt->rt_type == RTN_MULTICAST) {
		IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTMCAST, skb->len);
	} else if (rt->rt_type == RTN_BROADCAST)
		IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTBCAST, skb->len);

然后,对skb的头部长度进行检查,看其是否有足够的空间来添加别的协议头部(报文的发送要一层层的添加报文头部),长度不够的话则进行空间的分配。

	/* Be paranoid, rather than too clever. */
	if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
		struct sk_buff *skb2;

		skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
		if (!skb2) {
			kfree_skb(skb);
			return -ENOMEM;
		}
		if (skb->sk)
			skb_set_owner_w(skb2, skb->sk);
		consume_skb(skb);
		skb = skb2;
	}

这部分的代码好像是处理什么网络设备的,类似虚拟网卡啥的,还不太清楚,后面再研究。

	if (lwtunnel_xmit_redirect(dst->lwtstate)) {
		int res = lwtunnel_xmit(skb);

		if (res < 0 || res == LWTUNNEL_XMIT_DONE)
			return res;
	}

最后,函数会根据报文的路由来查询skb的下一跳,并根据下一跳来获取报文的邻居子系统缓存。如果缓存不存在,则创建新的缓存。邻居子系统的作用是根据下一跳的IP地址来设置报文的MAC地址,一般查询到的结果会保存到缓存里,所以只有当第一次向主机发送报文时才会进行邻居子系统的创建。获取到邻居子系统后,通过调用neigh_output函数来将报文交给邻居子系统进行发送。

	rcu_read_lock_bh();
	nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);
	neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
	if (unlikely(!neigh))
		neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
	if (!IS_ERR(neigh)) {
		int res;

		sock_confirm_neigh(skb, neigh);
		res = neigh_output(neigh, skb);

		rcu_read_unlock_bh();
		return res;
	}
	rcu_read_unlock_bh();

	net_dbg_ratelimited("%s: No header cache and no neighbour!\n",
			    __func__);
	kfree_skb(skb);
	return -EINVAL;
}

neigh_output

这个函数首先对邻居的状态进行了检查,如果状态为NUD_CONNECTED的话则调用neigh_hh_output函数。状态为NUD_CONNECTED意味着这是以下情况之一:

  1. NUD_PERMANENT:这是一个静态路由;
  2. NUD_NOARP:不需要ARP(多播、组播等报文);
  3. NUD_REACHABLE:邻居可以到达,即通过IP查询到了MAC地址。

在邻居的状态为NUD_CONNECTED,且hh(还不知道是干啥的)已缓存的情况下,neigh_hh_output函数会被调用,否则邻居的output函数会被调用。

static inline int neigh_output(struct neighbour *n, struct sk_buff *skb)
{
	struct hh_cache *hh = &n->hh;

	if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)
		return neigh_hh_output(hh, skb);
	else
		return n->output(n, skb);
}

neigh_hh_outputn->output函数最终都会调用dev_queue_xmit函数来将报文交给网卡驱动处理。下面我们来看一下这两个函数都做了什么。

neigh_hh_output

static inline int neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)
{
	unsigned int hh_alen = 0;
	unsigned int seq;
	unsigned int hh_len;

	do {
		seq = read_seqbegin(&hh->hh_lock);
		hh_len = hh->hh_len;
		if (likely(hh_len <= HH_DATA_MOD)) {
			hh_alen = HH_DATA_MOD;

			/* skb_push() would proceed silently if we have room for
			 * the unaligned size but not for the aligned size:
			 * check headroom explicitly.
			 */
			if (likely(skb_headroom(skb) >= HH_DATA_MOD)) {
				/* this is inlined by gcc */
				memcpy(skb->data - HH_DATA_MOD, hh->hh_data,
				       HH_DATA_MOD);
			}
		} else {
			hh_alen = HH_DATA_ALIGN(hh_len);

			if (likely(skb_headroom(skb) >= hh_alen)) {
				memcpy(skb->data - hh_alen, hh->hh_data,
				       hh_alen);
			}
		}
	} while (read_seqretry(&hh->hh_lock, seq));

	if (WARN_ON_ONCE(skb_headroom(skb) < hh_alen)) {
		kfree_skb(skb);
		return NET_XMIT_DROP;
	}

	__skb_push(skb, hh_len);
	return dev_queue_xmit(skb);
}

参考链接:
Linux IP Networking
Monitoring and Tuning the Linux Networking Stack: Sending Data

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TCP/IP详解·卷2:实现》完整而详细地介绍了TCP/IP协议是如何实现的。书中给出了约500个图例,15000行实际操作的C代码,采用举例教学的方法帮助你掌握TCP/IP实现。《TCP/IP详解·卷2:实现》不仅说明了插口API和协议族的关系以及主机实现与路由器实现的差别。还介绍了4.4BSD-Lite版的新的特点。《TCP/IP详解·卷2:实现》适用于希望理解TCP/IP协议如何实现的人,包括编写网络应用程序的程序员以及利用TCP/IP维护计算机网络的系统管理员。 目录 · · · · · · 第一章 概述 1.1 引言 1.2 源代码表示 1.3 历史 1.4 应用编程接口 1.5 程序示例 1.6 系统调用和库函数 1.7 描述符 1.8 网络实现概述 1.9 mbuf与输出处理 1.10 输入处理 1.11 网络实现概述 1.12 中断级别与并发 1.13 源代码组织 1.14 测试网络 1.15 小结 第二章 mduf:存储器缓存 2.1 引言 2.2 代码介绍 2.3 mduf的定义 2.4 mduf结构 2.5 简单的mduf宏和函数 2.6 m_devget和m_pullup函数 2.7 mduf宏和函数的小结 2.8 Net/3联网数据结构小结 2.9 m_copy和簇引用记数 2.10 其他选择 2.11 小结 第三章 接口层 3.1 引言 3.2 代码介绍 3.3 ifnet结构 3.4 ifaddr结构 3.5 sockaddr结构 3.6 ifnet与ifaddr的专用化 3.7 网络初始化概述 3.8 以太网初始化 3.9 SLIP初始化 3.10 环回初始化 3.11 if_attach函数 3.12 ifinit函数 3.13 小结 第四章 接口:以太网 4.1 引言 4.2 代码介绍 4.3 以太网接口 4.4 ioctl系统调用 4.5 小结 第五章 接口:SLIP和环回 5.1 引言 5.2 代码介绍 5.3 SLIP接口 5.4 环回接口 5.5 小结 第六章 IP编址 6.1 引言 6.2 代码介绍 6.3 接口和地址小结 6.4 sockaddr_in结构 6.5 in_ifaddr结构 6.6 地址指派 6.7 接口ioctl处理 6.8 internet实用函数 6.9 ifnet实用函数 6.10 小结 第七章 域和协议 7.1 引言 7.2 代码介绍 7.3 domain结构 7.4 protosw结构 7.5 IP的domain和protosw结构 7.6 pffindproto和pffindtype函数 7.7 pfctlinput函数 7.8 IP初始化 7.9 sysctl系统调用 7.10 小结 第八章 IP:网际协议 8.1 引言 8.2 代码介绍 8.3 IP分组 8.4 输入处理:ipintr函数 8.5 转发:ip_forward函数 8.6 输出处理:ip_output函数 8.7 Internet检验和:in_cksum函数 8.8 setsockopt和getsockopt系统调用 8.9 ip_sysctl函数 8.10 小结 第九章 IP选项处理 9.1 引言 9.2 代码介绍 9.3 选项格式 9.4 ip_dooptions函数 9.5 记录路由选项 9.6 源站和记录路由选项 9.7 时间戳选项 9.8 ip_insertoptions函数 9.9 ip_pcbopts函数 9.10 一些限制 9.11 小结 第十章 IP的分片与重装 10.1 引言 10.2 代码介绍 10.3 分片 10.4 ip_optcopy函数 10.5 重装 10.6 ip_optcopy函数 10.7 ip_slowtimo函数 10.8 小结 第十一章 ICMP:Internet控制报文协议 第十二章 IP多播 第十三章 IGMP:Internet组管理协议 第十四章 IP多播选路 第十五章 插口层 第十六章 插口I/O 第十七章 插口选项 第十八章 Radix树路由表 第十九章 选路请求和选路消息 第二十章 选路接口 第二十一章 ARP:地址解析协议 第二十二章 协议控制块 第二十三章 UDP:用户数据报协议 第二十四章 TCP:传输控制协议 第二十五章 TCP的定时器 第二十六章 TCP输出 第二十七章 TCP的函数 第二十八章 TCP的输入 第二十九章 TCP的输入(续) 第三十章 TCP的用户需求 第三十一章 BPF:BSD分组过滤程序 第三十二章 原始IP 结束语 附录A 部分习题的解答 附录B 源代码的获取 附录C RFC 1122的有关内容 参考文献

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值