一、环境说明
内核版本:Linux 3.10
内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且网页可全局搜索函数)
网卡:Intel的igb网卡
网卡驱动源码目录:drivers/net/ethernet/intel/igb/
二、L3层概览
本章主要介绍收包的流程,在L3层是如何处理的。
类型为ETH_P_IP类型的数据包,被传递到三层,调用ip_rcv函数。
三、ip_rcv函数
netif_receive_skb函数会把指向L3协议指针(skb->nh)设置在L2报头尾端。因此,IP层函数可以安全地将它转换成iphdr结构。
此时,skb->data指向L3报头。
ip_rcv的主要工作就是对封包做健康检查,然后调用Netfilter钩子。
// file: net/ipv4/ip_input.c int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { const struct iphdr *iph; u32 len; if (skb->pkt_type == PACKET_OTHERHOST) //丢弃掉不是发往本机的报文,网卡开启混杂模式会收到此类报文 goto drop; IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len); if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { //检查skb是否为share?是:则克隆报文(克隆报文,内存分配失败的话,该封包会被丢弃) IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS); goto out; } if (!pskb_may_pull(skb, sizeof(struct iphdr))) //确保skb->data所指的区域包含的数据区块至少和IP报头一样长,即确保skb还可以容纳标准的报头(即20字节) goto inhdr_error; iph = ip_hdr(skb); //重新获取IP头(pskb_may_pull可以改变缓冲区结构) if (iph->ihl < 5 || iph->version != 4) //ip头长度至少为20字节(ihl>=5,报头的尺寸是4字节的备注),只支持v4 goto inhdr_error; // 这项检查会拖到现在才做,是因为此函数必须先确定基本报头没有被截断,而且从中读取东西前已通过基本健康检查 if (!pskb_may_pull(skb, iph->ihl*4)) //重复先前做过的检查,只不过这一次使用的是完整的ip报头尺寸。 goto inhdr_error; iph = ip_hdr(skb); if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) //ip头csum校验 goto csum_error; len = ntohs(iph->tot_len); //取ip分组总长,即ip首部加数据的长度 if (skb->len < len) { //skb的实际总长度小于ip分组总长,则drop(由于L2协议会填补有效载荷,Ethernet数据帧最小64字节) IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS); goto drop; } else if (len < (iph->ihl*4)) //确保封包的尺寸至少和IP报头的尺寸一样大(IP头不能分段,每个IP片段至少包含一个IP报头) goto inhdr_error; /* 调整数据包结构与校验和 * 检查是否有:L2协议填充封包以达到特定的最小长度? * 有:把封包剪成正确尺寸,再让L4校验和失效,以免进行接收的NIC计算 */ if (pskb_trim_rcsum(skb, len)) { // IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS); goto drop; } /* Remove any debris in the socket control block */ memset(IPCB(skb), 0, sizeof(struct inet_skb_parm)); //清空cb,即inet_skb_parm值 /* Must drop socket now because of tproxy. */ skb_orphan(skb); //调用netfilter,实现iptables功能,通过后调用ip_rcv_finish函数 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); csum_error: IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_CSUMERRORS); inhdr_error: IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS); drop: kfree_skb(skb); out: return NET_RX_DROP; }
ip_rcv()函数首先是对数据包类型进行检查,如果是PACKET_OTHERHOST类型,则直接丢弃。
skb_share_check()函数检查能否共享数据包结构?能:克隆一个新的数据包结构,函数使用这个新的数据包结构。克隆的数据包可以进行修改。
每一个数据包必须包含一个完整的IP头部,数据块中至少应该包含IP头部,pskb_may_pull()函数检查它是否包含IP头部。
ip_hdr()函数从数据包中取得IP头部,然后对头部进行检查。
ip_fast_csum()函数检查IP头部的校验和。接下来检查数据包的长度是否与头部记录的长度相符,它至少应该等于数据包头部的长度。
接着调用pskb_trim_rcsum()函数为传输层调整数据包与校验和。
ip_rcv()函数最后通过HF_HOOK宏,调用netfilter,实现iptables功能,通过后调用ip_rcv_finish函数。
四、ip_rcv_finish函数
标签:IP,收包,ip,Linux,dev,L3,skb,报头,数据包 From: https://www.cnblogs.com/573583868wuy/p/17926839.html