/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
struct iphdr *iph;
u32 len;
/*
* When the interface is in promisc mode, drop all the crap
* that it receives, do not try to analyse it.
* 当interface处于混杂模式,那么目的MAC地址是其它主机的数据包也会被网卡收上来,传递给ip_rcv函数.
* 有两个问题:1、其它主机是什么意思?http://blog.csdn.net/nerdx/article/details/12450055,获知是非本机、非广播、非
* 多播的数据帧。2、利用brctl命令可将主机配置成虚拟网桥,由此看来,经过网桥传输的数据包没有到IP层。
*/
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len); //从IPSTATS_MIB_IN的字面意,可知是对接收的IP包信息的统计
/*
* sk_buff结构有一个users成员,表示当前有users个模块在引用该sk_buff结构实体(注意是sk_buff本身,不包括它的数据缓冲
* 区)。具体谁引用的不需要都明白,至少,当前IPv4模块在引用它.
*
* 为了避免修改sk_buff实例时影响到其它模块,调用skb_share_check函数。它可以说做了三件小事:检查users的值是否等于1;
* 不等于1,则调用skb_clone整一个全新的sk_buff结构(不包括数据缓冲区); 最后,调用kfree_skb函数使得users-1。(当users
* 的值=0时,内存会被释放.)
* 问题:在利用netlink机制与用户态进行通信时,分别调用alloc_skb(不但分配sk_buff本身,还分配了数据缓冲区)、
* nlmsg_put(放置要发送的数据)、netlink_unicast(发送出去). 因为该skb是自己申请的,本以为在最后需要调用
* kfree_skb(skb)函数释放skb。结果程序崩溃了,原因由可知,
* netlink_unicast内部已经释放了该skb.
* NETfilter的回调函数是不需要释放skb的,即使返回了DROP也一样.
*/
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto out;
}
/*
* pskb_may_pull的实现还是挺复杂的,但是它的目的很简单:确保线性数据缓冲区中有sizeof(struct iphdr))个字节.
* 这牵扯到了sk_buff中数据缓冲区中内容的移动,甚至有可能更换sk-buff的线性缓冲区(如果第二个参数的值大于len-skb_headle * n + skb->tail - skb->end). skb中缓冲区内容的顺序是:线性缓冲区+skb_shinfo(skb)->frag+skb_shinfo(skb)->frag_list。
* 相当于重新组织了skb的数据缓冲区.
* 该函数的具体实现过程,参见http://blog.chinaunix.net/uid-22577711-id-3220155.html。
* 注意:pskb_may_pull是会修改sk_buff和数据缓冲区的.
*/
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
iph = ip_hdr(skb);
/*
* RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
*
* Is the datagram acceptable?
*
* 1. Length at least the size of an ip header
* 2. Version of 4
* 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
* 4. Doesn't have a bogus length
*/
if (iph->ihl < 5 || iph->version != 4) //基本检查
goto inhdr_error;
if (!pskb_may_pull(skb, iph->ihl*4)) //确保线性缓冲区中有完整的IP报头
goto inhdr_error;
iph = ip_hdr(skb);
/*
* 对IP层校验和进行检查. 等于0是正常的. 否则直接丢弃掉该数据包.
*/
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
goto inhdr_error;
/*
* skb->len < len : 不可能出现的状况。如果报文是被分段传输过来的, 那么len也只是代表了该分段的大小. 而skb也一定
* 包含了整个分段的大小.
* len < (iph->ihl*4) :IP报文的tot_len值有问题
*
*/
len = ntohs(iph->tot_len);
if (skb->len < len) {
IP_INC_STATS_BH(dev_net(dev), 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).
* 当传递给L2的载荷太小,数据帧大小小于发送的最小值。那么L2会有填充动作。所以IP报文的大小应该以iph->tot_len为准
* 裁剪的时候也要考虑到是否有非线性缓冲区存在.
*/
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缓冲区留下一块净土,以后用来存放ip options解析结果
/* Must drop socket now because of tproxy. */
skb_orphan(skb); //make the @skb unowned, skb->destruct=NULL && skb->sk = NULL
return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
inhdr_error:
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS); //统计IP报头有问题的包
drop:
kfree_skb(skb); //users-1
out:
return NET_RX_DROP; //丢弃该报文
}
阅读(2038) | 评论(0) | 转发(0) |