深入理解Linux网络技术内幕学习笔记第十九章:因特网协议第四版(IPv4):Linux的原理和功能

本章主要介绍Linux支持IP的数据结构和基本活动,如入口IP包如何传递至IP接收函数,校验和如何验证,以及IP选项如何处理。

主要的IPv4数据结构:

struct iphdr{

    

};//ip报头

struct ip_options{

};//此结构代表必须被传输或转发的封包选项,那些选项存储在此结构中,而不是struct iphdr结构中

struct ipcm_cookie{

    

};//此结构包含了传输封包所需的各种信息

struct ipq{

    

};//IP封包的片段集,参见第二十二章IP片段hash表的组织一节

struct inet_peer{

    

};//内核会为最近连接过的每个远程主机都保留一个这一结构的实例

struct ipstats_mib{

    

};//SNMP(简单网络管理协议)采用一种名为MIB(Management Information Base ,管理信息库)的对象来收集系统的相关统计数据。此结构会保存关于IP层的统计资料。

struct in_device{

    

};//此结构存储了一个网络设备所有与IPv4相关的配置内容。net_device结构中的ip_ptr指针指向该结构

struct in_ifaddr{

    

};//当在接口山配置一个IPv4地址时,内核会建立一个in_faddr结构

struct ipv4_devconf{

    

};//该结构用于调整网络设备的行为。每个设备都有一个实例。其字段通过/proc/sys/net/ipv4/conf输出

struct ipv4_config{

    

};//不同于ipv4_devconf存储每个设备的配置,该结构存储每个主机的配置

struct cork{

    

};//该结构用于存储套接字选项CORK选项。第二十一章会看到其字段如何应用

sk_buff和net_device结构里与校验和有关的字段:

net_device->features字段表明设备的能力,其中和控制检验和计算的一些标志如下:

    NETIF_F_NO_SUM:此设备很可靠,不需要使用任何L4校验和。回绕设备就开启了此c功能。

    NETIF_F_IP_CSUM:此设备可以在硬件中计算L4检验和,但是只针对使用IPv4的TCP,UDP。

    NETIF_F_HW_CSUM:此设备可以为任何协议在硬件中计算L4校验和。

skb_buff中主要有两个字段跟校验和有关:skb->csum和skb->ip_summed。

当一个封包被接收时,skb->csum可能包含其L4校验和,skb->ip_summed字段则会记录L4校验和的状态,这些状态代表设备驱动程序要告诉L4层的事,状态有下列这些值:

    CHECKSUM_NONE:csum中的校验和无效,需要L4层自己来计算。为社么csum中的校验和无效,因为①设备不提供硬件校验和计算。②校验和必须重新计算并验证。如第十八章“对L4校验和所做的修改”一节提到的情况。

    CHECKSUM_HW:NIC以L4报头和有效载荷计算了校验和,然后把校验和拷贝到skb->csum字段。软件(L4接收函数)需要把伪报头的校验和添加到skb->csum,并验证最后所得的校验和。

    CHECKSUM_UNNECESSARY:NIC已经计算了L4报头以及伪报头的校验和(伪报头的校验和可以由设备驱动程序在软件中计算),所以,软件(L4接收函数)无需再计算L4的校验和。

当一个封包被传输时,skb->csum不再是校验和本是,而是指向NIC要把它计算的校验和即将存放的地方,也就是说,封包传输期间,只有当校验和是在硬件中计算时,才会用到此字段。skb->ip_summed依然表示L4校验和状态:

    CHECKSUM_NONE:协议已经处理了校验和,设备不需要做任何事。

    CHECKSUM_HW:协议只把伪报头的校验和存储在报头中,设备应该添加L4报头和有效载荷的校验和。

封包的一般性处理:

协议初始化:

ipv4协议由ip_init函数初始化,该函数完成以下主要任务:

    为ip封包注册处理函数ip_rcv。(参见第十三章)

    初始化路由子系统,包括与协议无关的缓存。(参见第三十二章)

    初始化用于管理ip端点的基础架构(参见二十三章“长效ip端点信息”一节)

开机期间,ip_init会由inet_init调用。inet_init会处理所有与ipv4有关的子系统的初始化。

和Netfilter交互:

基本上,防火墙在网络堆栈程序中的某些地方都有钩子函数。当符合某些条件时,封包就会通过那些钩子函数。

与路由子系统的交互:

ip层在好几个地方必须和路由表交互。本章只简单说明ip层用于查询路由表的三个函数:

    ip_route_input:决定封包是被本地传递,转发或丢弃。

    ip_route_output_flow:传输封包前使用,此函数会返回下个跳点以及要使用的出口设备。

    dst_pmtu:给定一个路由表缓存项目,返回相关的PMTU。

上述函数会把路由表的查询结构存储在skb->dst中。

处理输入ip封包:

下面从ip_rcv函数开始分析内核网络协议栈内ip封包的路径。ip_rcv函数原型如下:

int ip_rcv(struct sk_buff *skb,struct net_device *dev,struct packet_type *pt);

在第十章和第十三章已经知道,NIC驱动如何设定L3协议标识符skb->protocol和封包类型skb->pkt_type。当L2的目的地址和接收接口的地址不同时,skb->pkt_type = PACKET_OTHERHOST,通常这些包会被抛弃去,若接口处于混杂模式,会把这些包传给相关的嗅探器。但是,到了L3,ip_rcv只会将其丢弃:

    if(skb->pkt_type == PACKET_OTHERHOST)

        goto drop;

skb_share_check会检查封包的引用计数是否大于1,若处理程序看见引用计数大于1,就会自己建立一份缓冲区副本,使其可以修改封包。

    if((skb = skb_share_check(skb,GFP_ATOMIC)) == NULL){

        IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);

        goto out;

    }

pskb_may_pull的工作是确保skb->data所指区域包含的数据至少和ip头一样大。如果条件符合,则无事可做,否则缺漏的部分就会从skb_shinfo(skb)->frags[]里的数据片段(如果有的话)拷贝过来 ,之后再次初始话iph(struct iphdr)。

    if(!pskb_may_pull(skb,sizeof(struct iphdr)))

        goto inhdr_error;

    iphdr = skb->nh.iph;

接着对ip报头做一些健康检查。

    if(iph->ihl < 5 || iph->version !=4)

        goto inhdr_error;

现在,重复先前做过的检查,但是这次是完整的ip头(包括选项)。

    if(!pskb_may_pull(skb,iph->ihl*4))

        goto inhdr_error;

    iph = skb->nh.iph;

接着计算校验和。

    if(ip_fast_csum((u8 *)iph,iph->ihl) != 0)

        goto inhdr_error;

然后继续检查,确保接收的封包长度大于或等于ip报头中记录的长度(因为L2层可能为了满足最小传输尺寸而做了填充,所以封包长度可能大于ip头中记录的长度)。同时确保封包的此少和ip报头一样大。

    {

        _ _u32 len = ntohs(iph->tot_len);

        if(skb->len < len || len < iph->ihl<<2))//<<2是因为报头的大小以4字节为单位,所以需要先乘以4

            goto inhdr_error;

//pskb_trim_rcsum用于检查L2是否填充了封包使其达到特定的最小长度,如果有,将其剪裁成正确的大小

        if(pskb_trim_rcsum(skb,len)){

            IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);

            goto drop;

        }

    }

最后来到函数的尾端,调用Netfilter子系统。

    return NF_HOOL(PF_INET,NF_IP_PRE_ROUTING,    skb , dev ,NULL,ip_rcv_finish);

Netfilter子系统(确切的说,是PF_INET,NF_IP_PRE_ROUTING位置)不决定丢弃封包,则后续会执行ip_rcv_finish函数。

ip_rcv_finish函数(static inline  int ip_rcv_finish(struct sk_buff *skb) ):

ip_rcv主要做一些基本的健康检查,ip_rcv_finish会处理主要工作:

    决定封包传给本地还是转发,若转发,还要找到出口设备和下个跳点。

    分析和处理一些ip选项,并非所有选项都在这处理。

skb->nh字段是在netif_receive_skb里初始化的,当时,还不知道L3协议,所以用nh.raw初始化,现在,可以取得指向IP报头的指针了。

    struct net_device *dev = skb->dev;

    struct iphdr *iph = skb->nh.iph;

skb->dst可能包含封包通往其目的的路由信息,如果没有,询问路由子系统。

    if(skb->dst == NULL){

        if(ip_route_input(skb,iph->daddr,iph->saddr,iph->tos,dev))

                goto drop;

    }

接着,更新Traffic Control(Qos层)所用的统计数据。

    #ifdef CONFIG_NET_CLS_ROUTE

     ...

    #endif

当ip报头的长度大于20字节时,有一些IP选项要处理。

    if(iph->ipl > 5){

        struct ip_options *opt;

        if(skb_cow(skb,skb_headroom(skb))){//skb_cow,如果缓冲区和别人共享,就会做出缓冲区副本

            IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);

            goto drop;

        }

        iph = skb->nh.iph;

//解析报头中的IP选项,将分析结果放在中skb->cb所指的私有数据域字段的ip_option结构内。

        if(ip_options_compile(NULL,skb))

            goto inhdr_error;

    }

在封包是来源地路由的情况下,内核必须检查该设备的配置是否允许使用该选项。一般情况下,默认是允许的。若设备配置不允许来源地路由选项,则该封包就被丢弃(但不会产生ICMP消息)。当设备允许IP源路由时,调用ip_options_rcv_srr函数设置skb->dst,决定使用哪个设备把该封包转发至来源地路由列表中的下一个跳点。

    if(opt->srr){

        ....

        if(ip_options_rcv_srr(skb))

            goto drop;

    }

ip_rcv_finish函数最后会调用dst_input,完成封包的处理。

IP选项处理:

并非一个封包的所有ip选项都必须在其所有片段中重复,下面是选项相关的主要API:

    ip_options_compile:分析IP报头中的一群选项,然后对一个ip_options结构的实例初始化。

    ip_options_build:对IP报头中选项部分做初始化,传输本地封包时会用到该函数。

    ip_options_fragment:第一个片段时唯一继承了原有封包所有选项的片段,其他片段则不会,但是会以空选项填充,使所有片段尺寸一样,这样可以简化分片流程。

    ip_forward_options:转发一个封包时,有些选项必须被处理。

    ip_options_get:此函数会接收一群选项,用ip_options_compile 解析,然后把结果存储在其分配的ip_options结构中。

    ip_options_echo:指定入口IP封包及其IP选项后,此函数就可以建立用于回复传送者的IP选项。

ip_options_compile函数:

原型:int ip_options_compile(struct ip_options *opt ,struct sk_buff *skb)

当skb不为NULL时(本例中opt为NULL),表示正在处理入口封包。当skb为NULL时(本例中,opt不为NULL),表示正在处理本地传输的封包。

在传输一个本地封包时,opt不为NULL,opt->data包含一个指向IP报头的指针。处理入口封包时,opt为NULL,报头包含在skb中,ip_options结构存储在skb->cb。ip_options_compile函数会根据IP报头位于何处而对本地变量做初始化。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值