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

转载 2013年08月23日 09:45:38
首先来看校验相关的一些结构: 

1 net_device结构: 

包含一个features的域,这个表示设备的一些特性(比如控制校验),下面的几个flag就是用来控制校验: 
#define NETIF_F_IP_CSUM     2   /* Can checksum TCP/UDP over IPv4. */  
#define NETIF_F_NO_CSUM     4   /* Does not require checksum. F.e. loopack. */  
#define NETIF_F_HW_CSUM     8   /* Can checksum all the packets. */  
#define NETIF_F_IPV6_CSUM   16  /* Can checksum TCP/UDP over IPV6 */

每个flags的介绍,注释里面都写得很清楚,这里就不一一解释了。这里要注意的是NETIF_F_HW_CSUM,他其实表示在硬件上为所有协议校验。 

2 sk_buff: 

skb->csum和skb->ip_summed这两个域也是与校验相关的,这两个域的含义依赖于skb表示的是一个输入包还是一个输出帧。 

当数据包是一个输入包时,skb->csum表示的是当前数据包的4层的checksum值,skb->ip_summed表示的是四层校验的状态,下面的几个宏定义表示了设备驱动传递给4层的一些信息(通过ip_sumed),这里要注意,一旦当四层接受了这个包,他可能会改变ip_summed的值。 
/* Don't change this without changing skb_csum_unnecessary! */  
#define CHECKSUM_NONE 0  
#define CHECKSUM_UNNECESSARY 1  
#define CHECKSUM_COMPLETE 2 

CHECKSUM_NONE表示csum域中的校验值是错误的,也就是校验失败。这里要注意的是,一般来说当2层的校验失败后,驱动会直接丢掉这个包,可是如果输入帧是要被forward的,那么路由器不应该由于一个四层的校验失败而丢掉这个包(路由器不建议查看四层的校验值),它将会将这位置为CHECKSUM_NONE,然后将包发向目的地址,交由目的地址的主机来进行处理。 

CHECKSUM_UNNECESSARY表示网卡已经计算和验证了四层的头和校验值。也就是计算了tcp udp的伪头。还有一种情况就是回环,因为在回环中错误发生的概率太低了,因此就不需要计算校验来节省cpu事件。 

CHECKSUM_COMPLETE表示nic已经计算了4层头的校验,并且csum已经被赋值,此时4层的接收者只需要加伪头并验证校验结果。 

接下来我们来看当数据包是输出包时的情况,此时csum表示为一个指针,它表示硬件网卡存放将要计算的校验值的地址。这个域在输出包时使用,只在校验值在硬件计算的情况下。比如NAT,它会修改ip头,此时就需要重新计算4层的校验值,也就是从4层传递下来的4层校验值需要在底层进行修改。当修改后,我们在底层就可以通过csum来存取这个校验值。 

而此时ip_summed可以被设置的值有下面两种:

#define CHECKSUM_NONE 0  
#define CHECKSUM_COMPLETE 2 

这时含义就完全不一样了。第一个表示已经计算好了校验值,设备不需要做任何事。 

第二个表示4层的伪头的校验已经完毕,并且已经加入到ip头中,此时只需要设备计算整个头4层头的校验值。 


主要来看一下ip输入数据包的处理,也就是ip协议处理函数。 

具体的协议注册什么的,可以看我前面的blog,这里我们知道处理ip输入的函数是ip_rcv. 

先来看下当执行ip_rcv执行之前,sk_buff的结构: 


 

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;  
  	/*我们知道当为PACKET_OTHERHOST是,2层就会直接丢掉所有的包,
	可是如果网卡被设置为混杂模式,此时包就会传递到3层,这个时侯内核会有hook来处理这个,而我们这里就只需要直接丢掉所有的包。*/  
    if (skb->pkt_type == PACKET_OTHERHOST)  
        goto drop;  
  
    IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INRECEIVES);  
  
	//检测这个数据包是否被内核其他部分使用,也就是监测引用计数。如果有被其他部分使用,则直接复制一份副本,然后返回。  
    if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {  
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);  
        goto out;  
    }  
  
	/*检测skb->data的数据至少要和ip头大小一样。
	这个原因很简单,每个包都必须包含一个ip头,如果比ip头还小,说明包头有错误了。 */ 
    if (!pskb_may_pull(skb, sizeof(struct iphdr)))  
        goto inhdr_error;  
	//取出ip头  
    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 
     */  
  
	/*ip头的ihl域表示ip头的大小(就是也就是IP层头部包含多少个32位),version表示ip协议版本,
	这里第一个检测的原因是基本ip头的大小是20个字节,也就是最小为20个字节,20*8/32=5,所以最小必须是5。
	而这里版本,由于这个只处理ipv4,因此version必须是4.  */
    if (iph->ihl < 5 || iph->version != 4)  
        goto inhdr_error;  
  
	//这次来检测整个ip头的大小(包括option)和skb->data.这个检测到这里才执行,是因为,必须首先确定ip头的基本正确。  
    if (!pskb_may_pull(skb, iph->ihl*4))  
        goto inhdr_error;  
  
    iph = ip_hdr(skb);  
  
	//开始校验ip头,也就是开始三层校验。  
    if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))  
        goto inhdr_error;  
	//取出整个ip头的长度(包括option)  
    len = ntohs(iph->tot_len);  
	/*接下来的检测是因为在2层由于要满足最小帧的大小,因此可能会填充一些空数据,而三层ip头计算长度时,
	会忽略这些空数据,因此这里的skb->len一定是大于或等于len  */
    if (skb->len < len) {  
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);  
        goto drop;  
    } else if (len < (iph->ihl*4))   
	//这个判断是因为ip头不能被切包,也就是每个切好的包必须至少包含一个ip头。  
        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). 
     */  
	//这里也就是我们上面说的情况,需要把skb->len和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));  
  
	//调用net filter hook。  
    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);  
drop:  
    kfree_skb(skb);  
out:  
    return NET_RX_DROP;  
}



我们这里先不详细介绍net filter,这里我们只需要知道,在NF_HOOK中,会检测每个包(通过用户空间设置的规则)然后来决定要不要这个数据包通过。最后如果允许的话,就会调用 ip_rcv_finish函数。所以这里我们详细看下 ip_rcv_finish函数: 

它主要会做两件事: 

1 决定这个包是被传递给高层,还是被forward。 

2 解析并执行一些ip option。 
static int ip_rcv_finish(struct sk_buff *skb)  
{  
    const struct iphdr *iph = ip_hdr(skb);  
    struct rtable *rt;  
  
    /* 
     *  Initialise the virtual path cache for the packet. It describes 
     *  how the packet travels inside Linux networking. 
    */  
  
	//查找路由表的相关操作。  
    if (skb->dst == NULL) {  
	//查找路由。这里也会初始化skb->dst->input。  
    int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);  
        if (unlikely(err)) {  
            if (err == -EHOSTUNREACH)  
                IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INADDRERRORS);  
            else if (err == -ENETUNREACH)  
                IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INNOROUTES);  
            goto drop;  
        }  
    }  
  
	//QOS的相关操作.  
#ifdef CONFIG_NET_CLS_ROUTE  
    if (unlikely(skb->dst->tclassid)) {  
        struct ip_rt_acct *st = per_cpu_ptr(ip_rt_acct, smp_processor_id());  
        u32 idx = skb->dst->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  
  
  
	//当ihl比5大,意味着有option。因此调用ip_rcv_options来进行解析和执行。  
    if (iph->ihl > 5 && ip_rcv_options(skb))  
        goto drop;  
    rt = skb->rtable;  
    if (rt->rt_type == RTN_MULTICAST)  
        IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCASTPKTS);  
    else if (rt->rt_type == RTN_BROADCAST)  
        IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCASTPKTS);  
  
	/*最后调用skb->dst->input,而这个虚函数的的值,首先是在ip_route_input中赋值,然后在ip_rcv_options也有可能被修改。
	这个虚函数要么被ip_local_deliver(也就是直接发向高层),要么是ip_forward(直接被forward).这两个函数以后会详细介绍。*/	
    return dst_input(skb);  
  
drop:  
    kfree_skb(skb);  
    return NET_RX_DROP;  
}  

  1. }  

相关文章推荐

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

这次主要介绍的是ip层的切片与组包的实现。  首先来看一下分片好的帧的一些概念:  1 第一个帧的offset位非0并且MF位为1  2 所有的在第一个帧和最后一个帧之间的帧都拥有长...
  • lmjjw
  • lmjjw
  • 2013年08月15日 22:37
  • 1136

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

这次我们来看数据包如何从4层传递到3层。  先看下面的图,这张图表示了4层和3层之间(也就是4层传输给3层)的传输所需要调用的主要的函数:    我们注意到3层最终会把帧用dst...

深入浅出linux tcp_ip协议栈4

  • 2012年06月27日 17:37
  • 10.29MB
  • 下载

Linux下TCP/IP协议栈的简单脉络分析

最近在写网络编程方面的一些东西,然后遇到了关于传输上的小问题。由于之前有简单的看过一些TCP/IP详解的一些东西,所以索性就找了本《追踪LinuxTCP/IP代码运行》的书看了一上午,结果发现初次接触...

linux下利用cp/ip协议简单的局域网聊天程序

客户端: #include #include #include #include #include #include #include #include #inclu...

Linux 中TCP/IP协议实现及嵌入式应用

  • 2017年04月06日 11:48
  • 21.11MB
  • 下载

Linux TCP/IP协议栈之Socket的实现分析 第一部份 Socket套接字的创建 (-)

内核版本:2.6.12 作者:kendo 版权所有,转载请注明出处[www.skynet.org.cn]; 说明:这仅仅是一个笔记,由于偶的水平有限,我甚至不能保证其中内容正确率超过80%。另外...
  • klarclm
  • klarclm
  • 2012年07月25日 12:51
  • 925

Linux TCP/IP协议栈之Socket的实现分析

数据包的接收 作者:kendo http://www.skynet.org.cn/viewthread.php?tid=14&extra=page%3D1 Kernel:2.6.12 ...

Linux内核--网络栈实现分析(四)--网络层之IP协议(上)

本文分析基于Linux Kernel 1.2.13原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7514017更多请看专栏,地址ht...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:linux下ip协议(V4)的实现(一)
举报原因:
原因补充:

(最多只允许输入30个字)