Linux 内核网络协议栈 ------ Linux 内核路由机制(二)之 ip层开始 -> 直到包被处理

接上面两篇:点击打开链接

                     点击打开链接


先看看ip头结构:

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)   // 小端
         __u8    ihl:4,                 // 首部长度(4位):首部长度指的是IP层头部占32 bit字的数目(也就是IP层头部包含多少个4字节 -- 32位),包括任何选项
                 version:4;             // 版本(4位),目前的协议版本号是4,称作IPv4。(注意头最长是:当4位全部取1即1111=15,那么15*32/8=60B)
 #elif defined (__BIG_ENDIAN_BITFIELD)  // 大端:调换位置
         __u8    version:4,             // 因为这两个字段共享一个字节,所以必须要区分大小端
                 ihl:4;
 #else
 #error  "Please fix <asm/byteorder.h>"
 #endif
         __u8    tos;                   // 服务类型字段(8位): 服务类型(TOS)字段包括一个3 bit的优先权子字段,4 bit的TOS子字段和1 bit未用位但必须置0。4 bit的TOS子字段分别代表:最小时延、最大吞吐量、最高可靠性和最小费用。4 bit中只能设置其中1 bit。如果所有4 bit均为0,那么就意味着是一般服务。
         __be16  tot_len;               // 总长度字段(16位)是指整个IP数据报的长度,以字节为单位。
         __be16  id;                    // 标识字段(16位)唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。
         __be16  frag_off;              // frag_off域的低13位 -- 分段偏移(Fragment offset)域指明了该分段在当前数据报中的什么位置上。除了一个数据报的最后一个分段以外,其他所有的分段(分片)必须是8字节的倍数。这是8字节是基本分段单位。iphdr->frag_off的高3位(1) 比特0是保留的,必须为0;(2) 比特1是“更多分片”(MF -- More Fragment)标志。除了最后一片外,其他每个组成数据报的片都要把该比特置1。 (3) 比特2是“不分片”(DF -- Don't Fragment)标志,如果将这一比特置1,IP将不对数据报进行分片,这时如果有需要进行分片的数据报到来,会丢弃此数据报并发送一个ICMP差错报文给起始端。
         __u8    ttl;                   // 生存时间字段设置了数据报可以经过的最多路由器数
         __u8    protocol;              // 协议字段(8位):指明了该将它交给哪个传输进程。TCP是一种可能,但是UDP或者其他的协议也是可能的。
         __sum16 check;                 // 首部检验和字段(16位)是根据IP首部计算的检验和码。它不对首部后面的数据进行计算。
         __be32  saddr;                 // 源IP地址
         __be32  daddr;                 // 目的ip地址
         /*The options start here. */   // 
 };

下面从开始ip_rcv函数开始:

/*
 *      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;
 
         if (dev->nd_net != &init_net)     // 看一下初始化的网络空间是不是包含这个设备,不匹配则丢包
                 goto drop;
 
         /* When the interface is in promisc. mode, drop all the crap
          * that it receives, do not try to analyse it.
          *///注意当包类型是PACKET_OTHERHOST时候,上一层就会直接丢掉所有的包,如果网卡此时被设置为promisc混杂模式,此时包就会传递到3层, 这个时侯内核会有hook函数来处理这个,而这里就只需要直接丢掉所有的包!
         if (skb->pkt_type == PACKET_OTHERHOST) // 类型查看在if_packet.h中
                 goto drop;
 
         IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES); // 给需要重组的碎片计数
 
         if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { // 检查缓冲区skb是不是共享的!如果共享,克隆一个返回给skb,如果不是原skb返回!
                 IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);        // 如果为NULL,那么返回内存分配失败!
                 goto out;                              // 检查是否共享就是检查是不是超过一个人引用了这个skb~~~
         }
         // 注意参数是skb和ip的头长度,如果skb比头长度还小,肯定哪里出错了!直接error,否则ok~
         if (!pskb_may_pull(skb, sizeof(struct iphdr)))
                 goto inhdr_error;
 
         iph = ip_hdr(skb);   // 得到ip头(具体放入ip的头看上面的分析)
 
         /*
          *      RFC1122: 3.1.2.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
          */
         // 根据上面的头的结构我们知道,头部长度4位,每一位是表示32位,头部最长60B;注意固定长度20B;那么就是20*8bit,即算出ihl=20*8/32=5即0101
         if (iph->ihl < 5 || iph->version != 4)  // 第二个版本就没什么问题了
                 goto inhdr_error;
        if (!pskb_may_pull(skb, iph->ihl*4))// 这次来检测整个ip头的大小(包括option)和skb->data这个检测到这里才执行是因为,必须首先确定ip头的基本正确
                 goto inhdr_error;
 
         iph = ip_hdr(skb);
         // 开始校验ip头
         if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
                 goto inhdr_error;
         // 获得总长度
         len = ntohs(iph->tot_len);
         if (skb->len < len) {    // 长度对不上,drop
                 IP_INC_STATS_BH(IPSTATS_MIB_INTRUNCATEDPKTS);
                 goto drop;
         } else if (len < (iph->ihl*4))   //  每个包必须至少包含一个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(IPSTATS_MIB_INDISCARDS);
                 goto drop;
         }
 
         /* Remove any debris in the socket control block */
         memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
 
         return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,  // 最后一句其实是很重要的!下面直接使用ip_rcv_finish函数进行处理!
                        ip_rcv_finish);
 
 inhdr_error:
         IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
 drop:
         kfree_skb(skb);
 out:
         return NET_RX_DROP;
 }


那么下面主要看ip_rcv_finish函数!

static int ip_rcv_finish(struct sk_buff *skb)  // 注意:包的去向:被转发;或者传递给上层
{
         const struct iphdr *iph = ip_hdr(skb);   // ip头
         struct rtable *rt;
 
         /*
          *      Initialise the virtual path cache for the packet. It describes
          *      how the packet travels inside Linux networking.
          */
         if (skb->dst == NULL) {    // 没有目的地址
                 int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,// 我表示这个函数相当重要!这是路由的入口函数!相当的重要的哦!(1)
                                          skb->dev);
                 if (unlikely(err)) {
                         if (err == -EHOSTUNREACH)
                                 IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
                         else if (err == -ENETUNREACH)
                                 IP_INC_STATS_BH(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
         // 首部商都大于20B,那么存在option那么需要解析skb的option,使用函数ip_rcv_options
         if (iph->ihl > 5 && ip_rcv_options(skb))   // 非常重要!!!!!!!!!!!!!!!!(2)此处不多说~~~~~~~~
                 goto drop;
 
         rt = (struct rtable*)skb->dst;       
         if (rt->rt_type == RTN_MULTICAST)    // 多播
                 IP_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);
         else if (rt->rt_type == RTN_BROADCAST)  // 广播
                 IP_INC_STATS_BH(IPSTATS_MIB_INBCASTPKTS);
 
         return dst_input(skb);   // 非常重要!在ip_route_input中赋值,然后在 ip_rcv_options也有可能被修改。 这个虚函数要么被ip_local_deliver(发向高层),要么是ip_forward(转发出去).                   // (3)
 
 drop:
         kfree_skb(skb);
         return NET_RX_DROP;
 }

那么下面分别看:ip_route_input  、  ip_rcv_options   、   dst_input 


ip_route_input  函数如下:

int ip_route_input(struct sk_buff *skb, __be32 daddr, __be32 saddr,
                    u8 tos, struct net_device *dev)
{
         struct rtable * rth;            // 路由表结构体,之前的博文中说过了这个结构体其实是在高速缓存中使用的!先找缓存,找不到再全局查找
         unsigned        hash;
         int iif = dev->ifindex;         // 设备编号
         struct net *net;                // 网络空间
 
         net = dev->nd_net;              // 设备属于的网络空间
         tos &= IPTOS_RT_MASK;           // 服务字段
         hash = rt_hash(daddr, saddr, iif); //源地址,目的地址,设备索引,由这三个字段得到路由表的hash值
 
         rcu_read_lock();    // 下面是在路由高速缓存中进行匹配!
         for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;   // 注意下面就是进行匹配
              rth = rcu_dereference(rth->u.dst.rt_next)) {    // 这个数组是全局的:static struct rt_hash_bucket    *rt_hash_table;
                 if (rth->fl.fl4_dst == daddr &&              // 这个数组在这里有介绍:http://blog.csdn.net/shanshanpt/article/details/19918171
                     rth->fl.fl4_src == saddr &&              // rth->u.dst.rt_next:找下一个路由表,其实就是循环~~~~~~~~找路由表~~~~~~~
                     rth->fl.iif == iif &&
                     rth->fl.oif == 0 &&
                     rth->fl.mark == skb->mark &&
                     rth->fl.fl4_tos == tos &&
                     rth->u.dst.dev->nd_net == net &&
                     rth->rt_genid == atomic_read(&rt_genid)) {    // 找到匹配的!
                         dst_use(&rth->u.dst, jiffies);            // 其实就是改变dist三个字段值:use++,最近一次时间更新为jiffies;__refcnt
                         RT_CACHE_STAT_INC(in_hit);
                         rcu_read_unlock();
                         skb->dst = (struct dst_entry*)rth;        // 更新dist目标
                         return 0;                                 // 已经找到,那么返回就OK
                 }
                 RT_CACHE_STAT_INC(in_hlist_search);
         }
         rcu_read_unlock();
 
         /* Multicast recognition logic is moved from route cache to here.
            The problem was that too many Ethernet cards have broken/missing
            hardware multicast filters :-( As result the host on multicasting
            network acquires a lot of useless route cache entries, sort of
            SDR messages from all the world. Now we try to get rid of them.
            Really, provided software IP multicast filter is organized
            reasonably (at least, hashed), it does not result in a slowdown
            comparing with route cache reject entries.
            Note, that multicast routers are not affected, because
            route cache entry is created eventually.
          */
         if (ipv4_is_multicast(daddr)) {       // 如果是组播地址
                 struct in_device *in_dev;
 
                 rcu_read_lock();
                 if ((in_dev = __in_dev_get_rcu(dev)) != NULL) {      // ipv4的具体数据
                         int our = ip_check_mc(in_dev, daddr, saddr,  // 检测是不是给我的组播
                                 ip_hdr(skb)->protocol);
                         if (our
 #ifdef CONFIG_IP_MROUTE                                              // 是组播成员       
                             || (!ipv4_is_local_multicast(daddr) &&   // 本地组播
                                 IN_DEV_MFORWARD(in_dev))
 #endif
                             ) {
                                 rcu_read_unlock();
                                 return ip_route_input_mc(skb, daddr, saddr,   // 返回组播处理!
                                                          tos, dev, our);      // 将数据从dev口发出去
                         }
                 }
                 rcu_read_unlock();
                 return -EINVAL;
         }
         return ip_route_input_slow(skb, daddr, saddr, tos, dev);   // 注意这个函数很重要!当缓存中没有匹配到&&不是组播数据!
 }


下面具体看看ip_route_input_slow函数:

/*
 *      NOTE. We drop all the packets that has local source
 *      addresses, because every properly looped back packet
 *      must have correct destination already attached by output routine.
 *
 *      Such approach solves two big problems:
 *      1. Not simplex devices are handled properly.
 *      2. IP spoofing attempts are filtered with 100% of guarantee.
 */
 
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
                                u8 tos, struct net_device *dev)
{
         struct fib_result res;
         struct in_device *in_dev = in_dev_get(dev);
         struct flowi fl = { .nl_u = { .ip4_u =                  // 获取键值
                                       { .daddr = daddr,
                                         .saddr = saddr,
                                         .tos = tos,
                                         .scope = RT_SCOPE_UNIVERSE,
                                       } },
                             .mark = skb->mark,
                             .iif = dev->ifindex };
         unsigned        flags = 0;
         u32             itag = 0;
         struct rtable * rth;
         unsigned        hash;
         __be32          spec_dst;
         int             err = -EINVAL;
         int             free_res = 0;
         struct net    * net = dev->nd_net;
 
         /* IP on this device is disabled. */
 
         if (!in_dev)       // 无相应设备
                 goto out;
 
         /* Check for the most weird martians, which can be not detected
            by fib_lookup.
          */
 
         if (ipv4_is_multicast(saddr) || ipv4_is_lbcast(saddr) ||     // 检查是不是多播 || 有限广播 || 本地回环
             ipv4_is_loopback(saddr))
                 goto martian_source;
 
         if (daddr == htonl(0xFFFFFFFF) || (saddr == 0 && daddr == 0)) // 检查地址是否错误
                 goto brd_input;
 
         /* Accept zero addresses only to limited broadcast;
          * I even do not know to fix it or not. Waiting for complains :-)
          */
         if (ipv4_is_zeronet(saddr))   // 源地址是0地址仅仅用在限制广播中
                goto martian_source;
 
         if (ipv4_is_lbcast(daddr) || ipv4_is_zeronet(daddr) ||   //目的地址为非法,零类或回环地址
             ipv4_is_loopback(daddr))
                 goto martian_destination;
 
         /*
          *      Now we are ready to route packet.
          */                                               // fib_lookup函数将会在下面具体讲!!!!!!!!!!!!!!!!!!!!!!!!!(1)
         if ((err = fib_lookup(net, &fl, &res)) != 0) {    // 这个函数式最重要的函数!!!
                 if (!IN_DEV_FORWARD(in_dev))              // 在fib中查询路由信息,将路由查询结果保存在fib_result结构的res中
                         goto e_hostunreach;               // 没有找到而且设备不能转发
                 goto no_route;   // 没找到,但是可以发出去
         }
         free_res = 1;    // 已经找到
 
         RT_CACHE_STAT_INC(in_slow_tot);
 
         if (res.type == RTN_BROADCAST)   // 路由类型是广播类型
                 goto brd_input;
 
         if (res.type == RTN_LOCAL) {     // 路由类型:发到本地的数据
                 int result;
                 result = fib_validate_source(saddr, daddr, tos ,         // 检查是否合法
                                              net->loopback_dev->ifindex,
                                              dev, &spec_dst, &itag);
                 if (result < 0)
                         goto martian_source;       // 不合法
                 if (result)
                         flags |= RTCF_DIRECTSRC;   // 合法:标记为直接src
                 spec_dst = daddr;                  // 将目的地赋值
                 goto local_input;                  // goto到本地发出
         } 
         // 注意上面的数据包是:是否本地投放数据包,现在是第二条,是否能转发
         if (!IN_DEV_FORWARD(in_dev))   // 检查是否允许转发
                goto e_hostunreach;
         if (res.type != RTN_UNICAST)   // 路由类型不是单播说明错误!因为在前面讲广播和多播都已经处理过了,现在只能是单播
                 goto martian_destination;

         err = ip_mkroute_input(skb, &res, &fl, in_dev, daddr, saddr, tos);  // 创建路由项,准备转发数据!!!重要函数!!!!!(2)
 done:   // 注意:这个是最后找到转发路由的出口哦!上面的创建输出路由会有一个返回值,就是下面的err
         in_dev_put(in_dev);      // 释放对这个dev的引用
         if (free_res)            // 如果找到新的路由条目
                 fib_res_put(&res);  // 把结果送过去
 out:    return err;
 
 brd_input:   //目的地址为广播地址或源和目的地址为0或路由结果显示为广播
         if (skb->protocol != htons(ETH_P_IP)) // 只能是IP协议才可以
                 goto e_inval;
 
         if (ipv4_is_zeronet(saddr))   // 源地址为0,选择一个地址
                 spec_dst = inet_select_addr(dev, 0, RT_SCOPE_LINK);
         else {
                 err = fib_validate_source(saddr, 0, tos, 0, dev, &spec_dst, // 否则检查源地址是否合法
                                           &itag);
                 if (err < 0)
                         goto martian_source;
                 if (err)
                         flags |= RTCF_DIRECTSRC;
         }
         flags |= RTCF_BROADCAST;   // 路由的目的地址是一个广播地址
         res.type = RTN_BROADCAST;  // 数据以广播方式发送出去
         RT_CACHE_STAT_INC(in_brd); // cache统计++
 
 local_input: // 本地输出
         rth = dst_alloc(&ipv4_dst_ops); // 分配本地的路由缓冲项
         if (!rth)
                 goto e_nobufs;
 
         rth->u.dst.output= ip_rt_bug;   // 输出到本地的包
         rth->rt_genid = atomic_read(&rt_genid); // 
 
         atomic_set(&rth->u.dst.__refcnt, 1); 
         rth->u.dst.flags= DST_HOST;   // 主机路由,不是广播和多播路由
         if (IN_DEV_CONF_GET(in_dev, NOPOLICY))
                 rth->u.dst.flags |= DST_NOPOLICY;
         rth->fl.fl4_dst = daddr;   // 注意下面是路由项的一些赋值操作
         rth->rt_dst     = daddr;
         rth->fl.fl4_tos = tos;
         rth->fl.mark    = skb->mark;
         rth->fl.fl4_src = saddr;
         rth->rt_src     = saddr;
 #ifdef CONFIG_NET_CLS_ROUTE
         rth->u.dst.tclassid = itag;
 #endif
         rth->rt_iif     =
         rth->fl.iif     = dev->ifindex;
         rth->u.dst.dev  = net->loopback_dev;   // 回环设备
         dev_hold(rth->u.dst.dev);
         rth->idev       = in_dev_get(rth->u.dst.dev);
         rth->rt_gateway = daddr;
         rth->rt_spec_dst= spec_dst;
         rth->u.dst.input= ip_local_deliver; // 注意这个函数赋值特别重要!!!如果是发给高层的话使用函数:ip_local_deliver,否则...forward
         rth->rt_flags   = flags|RTCF_LOCAL;
         if (res.type == RTN_UNREACHABLE) {  // 路由不可达
                rth->u.dst.input= ip_error;
                 rth->u.dst.error= -err;
                 rth->rt_flags   &= ~RTCF_LOCAL;
         }
         rth->rt_type    = res.type;
         hash = rt_hash(daddr, saddr, fl.iif);  // 通过桑字段获取找到路由表所需的路由表中的hash值
         err = rt_intern_hash(hash, rth, (struct rtable**)&skb->dst);   // 添加到hash表中( 更新cache )
         goto done;   // 
 
 no_route:   // 路由信息库查找失败时
         RT_CACHE_STAT_INC(in_no_route);  // 统计
         spec_dst = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE); // 
         res.type = RTN_UNREACHABLE;// 路由不可达
         if (err == -ESRCH)
                 err = -ENETUNREACH;
         goto local_input; // 再转到本地处理( 其实最终执行的是不可达的情况~ )
 
         /*
          *      Do not cache martian addresses: they should be logged (RFC1812)
          */
 martian_destination:   // 目的地址出错
         RT_CACHE_STAT_INC(in_martian_dst);
 #ifdef CONFIG_IP_ROUTE_VERBOSE
         if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
                 printk(KERN_WARNING "martian destination %u.%u.%u.%u from "
                         "%u.%u.%u.%u, dev %s\n",
                         NIPQUAD(daddr), NIPQUAD(saddr), dev->name);
 #endif
 
 e_hostunreach:
         err = -EHOSTUNREACH;   //记录统计信息,丢弃这个数据 
         goto done;
 
e_inval:
         err = -EINVAL;  // 返回后直接丢包
         goto done;
 
 e_nobufs:
         err = -ENOBUFS; // 同上
         goto done;
 
 martian_source:  // 源地址出错
         ip_handle_martian_source(dev, in_dev, skb, daddr, saddr);
         goto e_inval;
 }

我们需要注意:宏CONFIG_IP_MULTIPLE_TABLES  宏导致了两种方式的fib表初始化,所以存在多路由表和无多路由表的情况

> 多路由表情况:

下面看一个最重要的路由查找过程函数:fib_lookup函数

int fib_lookup(struct net *net, struct flowi *flp, struct fib_result *res)
{
         struct fib_lookup_arg arg = {
                 .result = res, 
         };
         int err;
 
         err = fib_rules_lookup(net->ipv4.rules_ops, flp, 0, &arg);  // 这个才是真的执行函数(注意第一个参数中包含了这个net中的ipv4的你又规则)
         res->r = arg.rule;  // 找到具体路由规则( 完全由上面函数决定 )
 
         return err;
}


下面看看fib_rules_lookup函数:

int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
                      int flags, struct fib_lookup_arg *arg)
{
         struct fib_rule *rule;
         int err;
 
         rcu_read_lock();
 
         list_for_each_entry_rcu(rule, &ops->rules_list, list) {  // 遍历所有规则
jumped:
                 if (!fib_rule_match(rule, ops, fl, flags))   // 如果不匹配继续处理~~~~~这个函数下面说(1)
                         continue;
 
                 if (rule->action == FR_ACT_GOTO) {   // 使用另一套规则
                         struct fib_rule *target;
 
                         target = rcu_dereference(rule->ctarget);
                         if (target == NULL) {
                                 continue;
                         } else {
                                 rule = target;  // 找到这个第二套规则
                                 goto jumped;    // 返回匹配
                         }
                 } else if (rule->action == FR_ACT_NOP)  // 没有指定动作,继续查找
                         continue;
                 else
                         err = ops->action(rule, fl, flags, arg);   // 路由规则处理函数?咋哪里初始化的?看下下下面fib4_rule_action(2)
 
                 if (err != -EAGAIN) {
                         fib_rule_get(rule);
                         arg->rule = rule;
                         goto out;
                 }
         }
 
         err = -ESRCH;
out:
         rcu_read_unlock();
 
         return err;
}

看看fib_rule_match函数:

static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,
                           struct flowi *fl, int flags)
{
         int ret = 0;
 
         if (rule->ifindex && (rule->ifindex != fl->iif))   // 检测设备id
                 goto out;
 
         if ((rule->mark ^ fl->mark) & rule->mark_mask)     // 掩码匹配
                 goto out;
 
         ret = ops->match(rule, fl, flags);   // 这个也是ops的match函数~但是和上面的action一样,是怎么初始化的呢?看下面
out:
         return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;
}

看看ops(注意是是ipv4的ops哦~~~)的action函数和match函数:

先看看初始化:

static struct fib_rules_ops fib4_rules_ops_template = {
     .family          = AF_INET,
     .rule_size     = sizeof(struct fib4_rule),
     .addr_size     = sizeof(u32),
     .action          = fib4_rule_action,    // 看看在这里哈~
     .match          = fib4_rule_match,      // 看看在这里哈~
     .configure     = fib4_rule_configure,
     .compare     = fib4_rule_compare,
     .fill          = fib4_rule_fill,
     .default_pref     = fib4_rule_default_pref,
     .nlmsg_payload     = fib4_rule_nlmsg_payload,
     .flush_cache     = fib4_rule_flush_cache,
     .nlgroup     = RTNLGRP_IPV4_RULE,
     .policy          = fib4_rule_policy,
     .owner          = THIS_MODULE,
};

下面看看:fib4_rule_action函数和fib4_rule_match函数:

static int fib4_rule_action(struct fib_rule *rule, struct flowi *flp,
                             int flags, struct fib_lookup_arg *arg)
{
         int err = -EAGAIN;
         struct fib_table *tbl;
 
         switch (rule->action) {   // 注意看看这个action,注意是上面在匹配规则的时候找到的那个规则说明了该怎么处理这个包
         case FR_ACT_TO_TBL:       // 正常去查找路由表吧
                 break;
 
         case FR_ACT_UNREACHABLE:  // 
                 err = -ENETUNREACH;
                 goto errout;
 
         case FR_ACT_PROHIBIT:
                 err = -EACCES;
                 goto errout;
 
         case FR_ACT_BLACKHOLE:
         default:
                 err = -EINVAL;
                 goto errout;
         }
 
         if ((tbl = fib_get_table(rule->fr_net, rule->table)) == NULL)   //  查找路由表函数( 1 )
                 goto errout;
  
         err = tbl->tb_lookup(tbl, flp, (struct fib_result *) arg->result); // 这个函数特别重要!!!下面会分析( 2 )
         if (err > 0)
                 err = -EAGAIN;
 errout:
         return err;      // 返回哦~
 }

先看看fib_get_table函数:

struct fib_table *fib_get_table(struct net *net, u32 id)
{
     struct fib_table *tb;
     struct hlist_node *node;
     struct hlist_head *head;
     unsigned int h;

     if (id == 0)
          id = RT_TABLE_MAIN;                    // 主路由表
     h = id & (FIB_TABLE_HASHSZ - 1);            // 获得hash表的idx

     rcu_read_lock();
     head = &net->ipv4.fib_table_hash[h];        // 获得这个net中ipv4下的hash表中对应的路由表的链接字段链表头部 ( 注意是由fib_new_table创建的 )
                                                 // 注意括号里面的补充是非常重要的!为什么呢?看下面!
     hlist_for_each_entry_rcu(tb, node, head, tb_hlist) { // 遍历这个找到的list
          if (tb->tb_id == id) {     // 注意tb_id字段意义:路由函数表id(例如本地LOCAL,主路由MAIN...)
               rcu_read_unlock();
               return tb;     // 返回找到的路由表
          }
     }
     rcu_read_unlock();
     return NULL;
}

面我们知道路由表都已经找到了,那么下面就需要在路由表中寻找该怎么处理这个数据包了,所以请看下面:

下面介绍tb_lookup:

那么我们怎么知道tb_lookup是什么函数呢?或者是关联到什么函数?记得上面说过table是通过fib_new_table创建的,那么这个函数里有什么可用的信息呢?

看这个函数fib_new_table(  小插曲哦~ )

struct fib_table *fib_new_table(struct net *net, u32 id)
 {
         struct fib_table *tb;
         unsigned int h;
 
         if (id == 0)
                 id = RT_TABLE_MAIN;
         tb = fib_get_table(net, id);
         if (tb)
                 return tb;
 
         tb = fib_hash_table(id);   // 看到这一句了~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~笑了~
         if (!tb)
                 return NULL;
         h = id & (FIB_TABLE_HASHSZ - 1);
         hlist_add_head_rcu(&tb->tb_hlist, &net->ipv4.fib_table_hash[h]);
         return tb;
 }

再看fib_hash_table函数:( 小插曲 )

struct fib_table *fib_hash_table(u32 id)
{
     struct fib_table *tb;

     tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash),   这里又冒出了个两个新fib相关的结构
               GFP_KERNEL);
     if (tb == NULL)
          return NULL;

     tb->tb_id = id;
     tb->tb_default = -1;
     tb->tb_lookup = fn_hash_lookup;    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~看到了么~~~~~~~
     tb->tb_insert = fn_hash_insert;    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~看到了么~~~~~~~
     tb->tb_delete = fn_hash_delete;    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~看到了么~~~~~~~
     tb->tb_flush = fn_hash_flush;
     tb->tb_select_default = fn_hash_select_default;
     tb->tb_dump = fn_hash_dump;
     memset(tb->tb_data, 0, sizeof(struct fn_hash));
     return tb;
}


那么我们知道tb_lookup其实就是fn_hash_lookup,下面看是看看:

fn_hash_lookup函数:

static int
fn_hash_lookup(struct fib_table *tb, const struct flowi *flp, struct fib_result *res)
{
         int err;
         struct fn_zone *fz;
         struct fn_hash *t = (struct fn_hash*)tb->tb_data;  // 也就是fib_table之后就是fn_hash结构 (路由区数组)
                                                            // ( 参见:http://blog.csdn.net/shanshanpt/article/details/19918171 )

         read_lock(&fib_hash_lock);
         for (fz = t->fn_zone_list; fz; fz = fz->fz_next) { // 遍历路由区[33] ( 不同的子网长度 )
                 struct hlist_head *head;
                 struct hlist_node *node;
                 struct fib_node *f;
                 __be32 k = fz_key(flp->fl4_dst, fz);       // 目标地址在该网络区的网络号  fl4_det&((fz)->fz_mask)
 
                 head = &fz->fz_hash[fn_hash(k, fz)];       // 获得这个网络号相等的所有节点信息链表(意思:里面保存的是网络号相等的点)
                 hlist_for_each_entry(f, node, head, fn_hash) {   // 遍历所有的节点
                         if (f->fn_key != k)                // 在网络号长度相等的情况下:看看别名是否相等(其实就是更加具体的路由信息来区别最终的路由!)
                                 continue;
 
                         err = fib_semantic_match(&f->fn_alias,   // 注意现在寻找最终的路由!( 因为别名中还有一个fib_info这个具体信息没有处理~~~ )
                                                  flp, res,
                                                  f->fn_key, fz->fz_mask,  // 下面看看这个函数~~~~~~
                                                  fz->fz_order);
                         if (err <= 0)
                                 goto out;
                 }
         }
         err = 1;
out:
         read_unlock(&fib_hash_lock);
         return err;
}
 

看看fib_semantic_match函数:

/* Note! fib_semantic_match intentionally uses  RCU list functions. */
int fib_semantic_match(struct list_head *head, const struct flowi *flp,
                        struct fib_result *res, __be32 zone, __be32 mask,
                         int prefixlen)
{
         struct fib_alias *fa;
         int nh_sel = 0;
 
         list_for_each_entry_rcu(fa, head, fa_list) {   // 遍历所有的别名
                 int err;
 
                 if (fa->fa_tos &&                      // tos
                     fa->fa_tos != flp->fl4_tos)
                         continue;

                 if (fa->fa_scope < flp->fl4_scope)     // 跳转范围
                         continue;
 
                 fa->fa_state |= FA_S_ACCESSED;
 
                 err = fib_props[fa->fa_type].error;   // 取转发类型错误码 ,根据错err进行特定处理
                 if (err == 0) {  // 允许转发
                         struct fib_info *fi = fa->fa_info;   // fib_info
 
                         if (fi->fib_flags & RTNH_F_DEAD)  // 该转发节点已死( 不允许转发 )
                                continue;
 
                         switch (fa->fa_type) {    // 路由类型
                         case RTN_UNICAST:   // 单播
                         case RTN_LOCAL:     // 本地
                         case RTN_BROADCAST: // 广播
                         case RTN_ANYCAST:   // 任意
                         case RTN_MULTICAST: // 多播
                                 for_nexthops(fi) { // 注意前面的博文说过info中可能有很多的操作,那么变量里这个表,然后处理
                                         if (nh->nh_flags&RTNH_F_DEAD)  //  注意这个地方为什么突然冒出一个nh?这都是for_nexthops宏中定义了的原因,具体看下面的代码就懂了~
                                                 continue;
                                         if (!flp->oif || flp->oif == nh->nh_oif) // 没有指定输出标识,那么就是说明没有输出设备,那么必然不会继续转发
                                                 break; // 如果输出标识和当前的相同,那么说明就是就是当前的口子进行路由转发~所以无需再找下一个了~
                                 }
 #ifdef CONFIG_IP_ROUTE_MULTIPATH            // 多路径路由
                                 if (nhsel < fi->fib_nhs) {
                                         nh_sel = nhsel;
                                         goto out_fill_res;   // 直接到下面
                                 }
 #else
                                 if (nhsel < 1) {             // 非多径路由转发地址编号必须小于1
                                         goto out_fill_res;   // 
                                 }
 #endif
                                 endfor_nexthops(fi);
                                 continue;
 
                         default:
                                 printk(KERN_WARNING "fib_semantic_match bad type %#x\n",
                                         fa->fa_type);
                                 return -EINVAL;
                         }
                 }
                 return err;
         }
         return 1;
 
 out_fill_res:                       // 看看下面的结构体就可以知道字段内容
         res->prefixlen = prefixlen;  
         res->nh_sel = nh_sel;
         res->type = fa->fa_type;
         res->scope = fa->fa_scope;
         res->fi = fa->fa_info;
         atomic_inc(&res->fi->fib_clntref);
         return 0;
 }

看一下这个结构体:

struct fib_result {
        unsigned char   prefixlen;   // 是网络地址长度
        unsigned char   nh_sel;      // 路由信息中下一跳的数量(有多路径时大于1)
        unsigned char   type;        // 路由类型
        unsigned char   scope;       // 跳转范围
#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED // 多路径路由
            __u32           network;    // 网络号
            __u32           netmask;    // 掩码
#endif
        struct fib_info *fi;         // 具体信息
#ifdef CONFIG_IP_MULTIPLE_TABLES
        struct fib_rule *r;      
#endif
    };

注意将多路由表说完了,下面看看单路由表情况:

> 非路由表情况:

static inline int fib_lookup(struct net *net, const struct flowi *flp,
                    struct fib_result *res)
{
     struct fib_table *table;

     table = fib_get_table(net, RT_TABLE_LOCAL); // 先查本地路由表( 看看这个函数,和多路由表的不一样~~~~~~~~ )
     if (!table->tb_lookup(table, flp, res))     // 这里面和上面的分析是一样的哦~~~~~~~~~~~~~~~~~~~~~~~
          return 0;

     table = fib_get_table(net, RT_TABLE_MAIN);  // 再查主路由表
     if (!table->tb_lookup(table, flp, res))     // 这里面和上面的分析是一样的哦~~~~~~~~~~~~~~~~~~~~~~~
          return 0;
     return -ENETUNREACH;
}

看看非多路由表情况下的fib_get_table函数:easy

static inline struct fib_table *fib_get_table(struct net *net, u32 id)
{
         struct hlist_head *ptr;
 
         ptr = id == RT_TABLE_LOCAL ?
                 &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] :    // 本地路由表
                 &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX];      // 主路由表
         return hlist_entry(ptr->first, struct fib_table, tb_hlist);
}

非多路由来说,到这里之后的分析和上面多路由的是一样的了~~~~~~~~~~~~


到这了我们就知道路由找到成功返回了( 结果在res ),或者失败~~~~那么我们将回到最初的上面的地方看看 应该是那个函数分析了!

现在应该看到的是:ip_route_input_slow  中的  ip_mkroute_input函数!!( 创建路由缓冲项 )

-----> 其实就是在告诉缓冲中创建一个新的路由项

static inline int ip_mkroute_input(struct sk_buff *skb,
                                    struct fib_result* res,
                                    const struct flowi *fl,
                                    struct in_device *in_dev,
                                    __be32 daddr, __be32 saddr, u32 tos)
{
         struct rtable* rth = NULL;
         int err;
         unsigned hash;
 
#ifdef CONFIG_IP_ROUTE_MULTIPATH
         if (res->fi && res->fi->fib_nhs > 1 && fl->oif == 0)
                 fib_select_multipath(fl, res);
#endif
 
         /* create a routing cache entry */
         err = __mkroute_input(skb, res, in_dev, daddr, saddr, tos, &rth);  // 先创建一个路由项(1),返回的路由在rth中
         if (err)
                 return err;
 
         /* put it into the cache */
         hash = rt_hash(daddr, saddr, fl->iif);   // 获得新的路由项的hash、值
         return rt_intern_hash(hash, rth, (struct rtable**)&skb->dst);  // 将新的路由项目插入(2)
}

看看__mkroute_input函数:

static inline int __mkroute_input(struct sk_buff *skb,
                                   struct fib_result* res,
                                   struct in_device *in_dev,
                                   __be32 daddr, __be32 saddr, u32 tos,
                                   struct rtable **result)
{
 
         struct rtable *rth;
         int err;
         struct in_device *out_dev;
         unsigned flags = 0;
         __be32 spec_dst;
         u32 itag;
 
         /* get a working reference to the output device */
         out_dev = in_dev_get(FIB_RES_DEV(*res));
         if (out_dev == NULL) {
                 if (net_ratelimit())
                         printk(KERN_CRIT "Bug in ip_route_input" \
                                "_slow(). Please, report\n");
                 return -EINVAL;
         }
 
 
         err = fib_validate_source(saddr, daddr, tos, FIB_RES_OIF(*res),    // 检查地址是否合法
                                   in_dev->dev, &spec_dst, &itag);
         if (err < 0) {
                 ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr,  // 处理非法情况
                                          saddr);
 
                 err = -EINVAL;
                 goto cleanup;
         }
 
         if (err)
                 flags |= RTCF_DIRECTSRC;
 
        if (out_dev == in_dev && err && !(flags & RTCF_MASQ) &&
            (IN_DEV_SHARED_MEDIA(out_dev) ||
              inet_addr_onlink(out_dev, saddr, FIB_RES_GW(*res))))
                 flags |= RTCF_DOREDIRECT;
 
         if (skb->protocol != htons(ETH_P_IP)) {          // 对于不是ip类型协议,例如是ARP协议,这个是不需要路由条目的~
                 /* Not IP (i.e. ARP). Do not create route, if it is
                  * invalid for proxy arp. DNAT routes are always valid.
                  */
                 if (out_dev == in_dev) {    // 出口就是当前的dev,那么直接输出,不需要创建
                         err = -EINVAL;
                         goto cleanup;
                 }
         }
 
 
         rth = dst_alloc(&ipv4_dst_ops);      // 分配路由缓存项( 下面会随便看一下这个函数 )
         if (!rth) {
                 err = -ENOBUFS;
                 goto cleanup;
         }
 
         atomic_set(&rth->u.dst.__refcnt, 1); // 初始化引用为1
         rth->u.dst.flags= DST_HOST;
         if (IN_DEV_CONF_GET(in_dev, NOPOLICY))
                 rth->u.dst.flags |= DST_NOPOLICY;
         if (IN_DEV_CONF_GET(out_dev, NOXFRM))
                 rth->u.dst.flags |= DST_NOXFRM;
         rth->fl.fl4_dst = daddr;             // 下面基本就是初始化rtable的各个成员
         rth->rt_dst     = daddr;
         rth->fl.fl4_tos = tos;
         rth->fl.mark    = skb->mark;
         rth->fl.fl4_src = saddr;
         rth->rt_src     = saddr;
         rth->rt_gateway = daddr;
         rth->rt_iif     =
         rth->fl.iif     = in_dev->dev->ifindex;
         rth->u.dst.dev  = (out_dev)->dev;
         dev_hold(rth->u.dst.dev);
         rth->idev       = in_dev_get(rth->u.dst.dev);
         rth->fl.oif     = 0;
         rth->rt_spec_dst= spec_dst;
 
         rth->u.dst.input = ip_forward;      // 注意这一句~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   入口函数
         rth->u.dst.output = ip_output;      // 注意这一句~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   输出函数
         rth->rt_genid = atomic_read(&rt_genid);
 
         rt_set_nexthop(rth, res, itag);
 
         rth->rt_flags = flags;
 
         *result = rth;     // 注意创建好的结果返回~~~~~~~~~~~~~~~~~~
         err = 0;
cleanup:
        /* release the working reference to the output device */
         in_dev_put(out_dev);
         return err;
}


要将一个缓存增加入hash表,首先要调用dst_alloc分配一个路由缓存项,分配的实质就是在SLAB中分配一个高速缓存节点,每次分配的时候,都会尝试垃圾回收:

void * dst_alloc(struct dst_ops * ops)
{
        struct dst_entry * dst;

        if (ops->gc && atomic_read(&ops->entries) > ops->gc_thresh) {  // 垃圾收集
                if (ops->gc(ops))
                        return NULL;
        }
        dst = kmem_cache_zalloc(ops->kmem_cachep, GFP_ATOMIC); // 在slab中分配缓存
        if (!dst)
                return NULL;
        // 初始化各成员
        atomic_set(&dst->__refcnt, 0);
        dst->ops = ops;
        dst->lastuse = jiffies;
        dst->path = dst;
        dst->input = dst->output = dst_discard;
#if RT_CACHE_DEBUG >= 2
        atomic_inc(&dst_total);
#endif
        atomic_inc(&ops->entries);
        return dst;  //返回
}


然后我们在看上面的将新的路由怎么插入我们的hash表呢?看 rt_intern_hash函数:

static int rt_intern_hash(unsigned hash, struct rtable *rt, struct rtable **rp)
{
         struct rtable   *rth, **rthp;
         unsigned long   now;
         struct rtable *cand, **candp;
         u32             min_score;
         int             chain_length;
         int attempts = !in_softirq();
 
restart:
         chain_length = 0;
         min_score = ~(u32)0;
         cand = NULL;
         candp = NULL;
         now = jiffies;
 
         rthp = &rt_hash_table[hash].chain;  // 全局的hash路由表中获得这个hash键值下的链 
         // 第一件事情就是检索要插入的缓存项在缓存hash表中是否存在。常理来讲,缓存的插入都是先查找但未命中后,再进行插入操作,所以这个检查好像是多余的。
         // 但是因为路由缓hash表可以在多个CPU上并行,缓存项可能在一个CPU上查找未命中的同时却被其它CPU插入
         spin_lock_bh(rt_hash_lock_addr(hash));
         while ((rth = *rthp) != NULL) {
			 if (rth->rt_genid != atomic_read(&rt_genid)) {  // 是否条目过期
                         *rthp = rth->u.dst.rt_next; // 指向下一个条目
                         rt_free(rth);    // 清理这个条目
                         continue;
                 }
			 if (compare_keys(&rth->fl, &rt->fl) && compare_netns(rth, rt)) { // 比较是否已经存在这个路由条目
                         /* Put it first */ 
				         // 下面其实就是:如果查找命中,则将它调到链首,
				         // 这样做的理由是因为它是最近被使用,有可能会在接下来的查找中最先被使用
                         *rthp = rth->u.dst.rt_next;  
                         /*
                          * Since lookup is lockfree, the deletion
                          * must be visible to another weakly ordered CPU before
                          * the insertion at the start of the hash chain.
                          */
                         rcu_assign_pointer(rth->u.dst.rt_next,
                                            rt_hash_table[hash].chain);
                         /*
                          * Since lookup is lockfree, the update writes
                          * must be ordered for consistency on SMP.
                          */
                         rcu_assign_pointer(rt_hash_table[hash].chain, rth);
 
                         dst_use(&rth->u.dst, now);  // 更新使用计数器和时间戳
                         spin_unlock_bh(rt_hash_lock_addr(hash)); // 解锁
 
                         rt_drop(rt);  // 因为存在所以无需插入
                         *rp = rth;
                         return 0;
                 }
                 // 下面需要怎么办?因为认为现在是否需删除这个条目,相当于将条目是换出cache。这
			     // 个计算使用下面的tr_score得到一个分数,进而得到一个最佳删除人选
                 if (!atomic_read(&rth->u.dst.__refcnt)) {
                         u32 score = rt_score(rth);  // 算分
 
                         if (score <= min_score) {  // 分数少,则作为新的候选被删除者 
                                 cand = rth;
                                 candp = rthp;
                                 min_score = score;
                         }
                 }
 
                 chain_length++; 
		 // 计数:为了后面判断是不是到了垃圾回收的阈值
		 // (意思就是,我们规定当这个链表中只要存在10个(阈值自己定,此处假设10)以上项,那么如果这个条目分数最低,
		 // 就是可以删除的~如果现在只有8个,那么即时你是分数最低的,也不要删除~~~)
 
                 rthp = &rth->u.dst.rt_next;  // 下一个,接着回去循环
         }

         if (cand) { // 如果找到了删除的候选人
	            // ( 需要删除候选人是因为避免换出溢出,如果不删除直接插入是可能的~~~注意多CPU情况下是不定的哦~
                 /* ip_rt_gc_elasticity used to be average length of chain
                  * length, when exceeded gc becomes really aggressive.
                  *
                  * The second limit is less certain. At the moment it allows
                  * only 2 entries per bucket. We will see.
                  */
			 if (chain_length > ip_rt_gc_elasticity) { // 是否可以删除呢?上面有解释的哦~ 
                         *candp = cand->u.dst.rt_next;   
                         rt_free(cand);  // 删除
                 }
         }
 
         /* Try to bind route to arp only if it is output
            route or unicast forwarding path.
          */
	//如果是单播中转或本地发出的报文,尝试将路由缓存与arp绑定,绑
	//定的理由在于加速,这样在数据发送的时候,可以很方便地封装二层帧首部
         if (rt->rt_type == RTN_UNICAST || rt->fl.iif == 0) {
                 int err = arp_bind_neighbour(&rt->u.dst); // 尝试绑定路由ARP,仅当它是输出路径或单播转发路径
                 if (err) {
                         spin_unlock_bh(rt_hash_lock_addr(hash));
 
                         if (err != -ENOBUFS) {
                                 rt_drop(rt);
                                 return err;
                         }
 
                         /* Neighbour tables are full and nothing
                            can be released. Try to shrink route cache,
                            it is most likely it holds some neighbour records.
                          */
			// 下面开始调整垃圾收回阀值,
			// 调用rt_garbage_collect进行主动垃圾清理,然后需要从上面开始重新判断
                         if (attempts-- > 0) {
                                 int saved_elasticity = ip_rt_gc_elasticity;
                                 int saved_int = ip_rt_gc_min_interval;
                                 ip_rt_gc_elasticity     = 1;
                                 ip_rt_gc_min_interval   = 0;
                                 rt_garbage_collect(&ipv4_dst_ops);
                                 ip_rt_gc_min_interval   = saved_int;
                                 ip_rt_gc_elasticity     = saved_elasticity;
                                 goto restart;
                         }
 
                         if (net_ratelimit()) // 超过最大尝试次数,还是失败,不尝试了~~~~直接丢掉~~~~~~
                                 printk(KERN_WARNING "Neighbour table overflow.\n");
                         rt_drop(rt);
                         return -ENOBUFS;
                 }
         }
 
         rt->u.dst.rt_next = rt_hash_table[hash].chain; // next指向chain首地址
#if RT_CACHE_DEBUG >= 2
         if (rt->u.dst.rt_next) {
                 struct rtable *trt;
                 printk(KERN_DEBUG "rt_cache @%02x: %u.%u.%u.%u", hash,
                        NIPQUAD(rt->rt_dst));
                 for (trt = rt->u.dst.rt_next; trt; trt = trt->u.dst.rt_next)
                         printk(" . %u.%u.%u.%u", NIPQUAD(trt->rt_dst));
                 printk("\n");
         }
#endif   
		 // 将首地址换成rt,相当于把rt插入到链首部~~~~ 
         rt_hash_table[hash].chain = rt;
         spin_unlock_bh(rt_hash_lock_addr(hash));// 解锁
         *rp = rt;
         return 0;
}


OK,现在缓冲中创建一个新的路由项创建OK,我们又该回到上面的什么地方呢????

也就是ip_route_input_slow 中 ip_mkroute_input完成,那么基本ip_route_input_slow 解释完成,那么在往上面看到了什么地方呢?ip_route_input 完成,

那么回到 ip_rcv_finish,现在基本就是找到了这个路由项,或者,没找到,找没找到都要往下面看,看ip_rcv_finish中下面的函数:开始看dst_input

/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{
         int err;
 
         for (;;) {
                 err = skb->dst->input(skb);     // 看到没有,调用目的的input函数,注意上面的分析我们知道input函数被赋值了~
                                                 // 那么基本就是两个函数:ip_forward和ip_local_deliver
                 if (likely(err == 0))           // 例如:rth->u.dst.input= ip_local_deliver;   rth->u.dst.input = ip_forward
                         return err; 
                 /* Oh, Jamal... Seems, I will not forgive you this mess. :-) */
                 if (unlikely(err != NET_XMIT_BYPASS))
                         return err;
         }
}


正题:先看本地转发数据:ip_forward

int ip_forward(struct sk_buff *skb)
{
         struct iphdr *iph;      /* Our header */
         struct rtable *rt;      /* Route we use */
         struct ip_options * opt = &(IPCB(skb)->opt);
 
         if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))   // 对数据包进行安全策略检查
                 goto drop;
         // forward第一步需要处理ip选项问题,至于ip选项,后期再说~~~~~~~~~~~~~~~~
         if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb)) // 处理ip的option:判断是否有Router_alter 选项(保存发送端的ip),若有,调用ip_call_ra_chain处理。   
                 return NET_RX_SUCCESS;
 
         if (skb->pkt_type != PACKET_HOST)   // 不是本地的包怎么可能在本地投放,所以drop
                 goto drop;                  // 在2层设置帧的类型,当帧的目的地址就是本机2层地址的时候,skb->pkt_type设置为PCAKET_HOST
 
         skb_forward_csum(skb);  // 其实就是:skb->ip_summed = CHECKSUM_NONE;
                                 // 因为现在就是在本地投放数据了,所以我们不需要在意4层的校验。设置ip_summed为CHECKSUM_NONE
         /*
          *      According to the RFC, we must first decrease the TTL field. If
          *      that reaches zero, we must reply an ICMP control message telling
          *      that the packet's lifetime expired.
          */
         if (ip_hdr(skb)->ttl <= 1)    // ttl使用完了,直接丢包
                 goto too_many_hops;
 
         if (!xfrm4_route_forward(skb)) //  IPsec(Internet Protocol Security)检测
                 goto drop;
 
         rt = (struct rtable*)skb->dst; // 获取路由表( 简单操作指针+地址偏移而已~ )
 
         if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway) // 判断是否是Strict源路由option。若是,看源路由option所制定的路由能否和rt_gateway(下一跳)匹配
                goto sr_failed;
 
         if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&
                      (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) { //检测相关域。出错则发送icmp,并丢弃这个包 
                 IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
                 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,  // 发送icmp报错
                           htonl(dst_mtu(&rt->u.dst)));
                 goto drop;
         }
 
         /* We are about to mangle packet. Copy it! */
         if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len)) // 防止skb共享的,所以此处copy一份,下面要做一些修改
                 goto drop;
         iph = ip_hdr(skb);   // 获得ip头
 
         /* Decrease ttl after skb cow done */
         ip_decrease_ttl(iph);   // ttl减少( 在ip头上 )
 
         /*
          *      We now generate an ICMP HOST REDIRECT giving the route
          *      we calculated.
          */
         if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb->sp) //如果我们所找到的下一跳地址比请求的更好的话,源host现在将会收到一个ICMP REDIRESCT消息(只有当源host没有请求 source routing option时)   
                 ip_rt_send_redirect(skb);   // 重定向发送
 
         skb->priority = rt_tos2priority(iph->tos);  // QOS的优先级设置
 
         return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
                        ip_forward_finish);        // 下面注要看ip_forward_finish函数!!!!!!!!!!!!!!!(重要!!!!)
 
 sr_failed:
         /*
          *      Strict routing permits no gatewaying
          */
          icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);   // 源路由设定网关于此不匹配
          goto drop;
 
 too_many_hops:
         /* Tell the sender its packet died... */    // ttl<1了,那么回复icmp消息
         IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
         icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
 drop:
         kfree_skb(skb);          // 丢包
         return NET_RX_DROP;
 }
 


看看ip_forward_finish函数:

static int ip_forward_finish(struct sk_buff *skb)
{
         struct ip_options * opt = &(IPCB(skb)->opt);
 
         IP_INC_STATS_BH(IPSTATS_MIB_OUTFORWDATAGRAMS);
 
         if (unlikely(opt->optlen))
                 ip_forward_options(skb);   // 处理一些ip的options,先不说
 
         return dst_output(skb);  // 这一函数注意了!dst_output函数最终调用skb->dst_output,如果是单播则是ip_output,多播则是ip_mc_output.如果有需要对包进行分割切片,那么也在这个函数处理~
}

先看看单播情况:

ip_output函数:

int ip_output(struct sk_buff *skb)
{
         struct net_device *dev = skb->dst->dev;
 
         IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
 
         skb->dev = dev;
         skb->protocol = htons(ETH_P_IP);
 
         return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,   // 最终执行的是ip_finish_output函数
                             ip_finish_output,
                             !(IPCB(skb)->flags & IPSKB_REROUTED));
}

看ip_finish_putput函数:

static int ip_finish_output(struct sk_buff *skb)
{
#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
         /* Policy lookup after SNAT yielded a new policy */
         if (skb->dst->xfrm != NULL) {
                 IPCB(skb)->flags |= IPSKB_REROUTED;
                 return dst_output(skb);
         }
#endif
         if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb)) // 网卡不支持GSO && 长度大于MTU,那么就需要分片哦~~~~~~,如果支持GSO,那么不需分片
                 return ip_fragment(skb, ip_finish_output2);     // 分片,之后使用ip_finish_output2处理转发
         else
                 return ip_finish_output2(skb);   // 直接使用ip_finish_output2处理转发
}

看看分片函数:ip_fragment函数

/*
 *      This IP datagram is too large to be sent in one piece.  Break it up into
 *      smaller pieces (each of size equal to IP header plus
 *      a block of the data of the original IP data part) that will yet fit in a
 *      single device frame, and queue such a frame for sending.
 */
 
int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))   // 首先我们要知道这个output函数指针就是ip_finish_output2函数指针
{
         struct iphdr *iph;
         int raw = 0;
         int ptr;
         struct net_device *dev;
         struct sk_buff *skb2;
         unsigned int mtu, hlen, left, len, ll_rs, pad;
         int offset;
         __be16 not_last_frag;
         struct rtable *rt = (struct rtable*)skb->dst;
         int err = 0;
 
         dev = rt->u.dst.dev;
 
         /*
          *      Point into the IP datagram header.
          */
 
         iph = ip_hdr(skb);  // ip头
         // 判断DF位,知道如果df位被设置了话就表示不要被分片,这时ip_fragment将会发送一个icmp报文返回
         if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) { 
                 IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
                 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,  // icmp报文返回
                           htonl(ip_skb_dst_mtu(skb)));
                 kfree_skb(skb);
                 return -EMSGSIZE;
         }
 
         /*
          *      Setup starting values.
          */
 
         hlen = iph->ihl * 4;  // 头部一位表示一个32位长度即4B,那么iph->ihl*4就是得到头部的长度
         mtu = dst_mtu(&rt->u.dst) - hlen;       /* Size of data space */  // 整体长度 - 头部长度 == MTU 
         IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;
 
         /* When frag_list is given, use it. First, check its validity:
          * some transformers could create wrong frag_list or break existing
          * one, it is not prohibited. In this case fall back to copying.
          *
          * LATER: this step can be merged to real generation of fragments,
          * we can switch to copy when see the first bad fragment.
          */// 如果4层将数据包分片了,那么就会把这些数据包放到skb的frag_list链表中, 因此这里首先先判断frag_list链表是否为空
         if (skb_shinfo(skb)->frag_list) {   // skb_shinfo这是什么东西?#define skb_shinfo(SKB)((struct skb_shared_info *)((SKB)->end))
                 struct sk_buff *frag;       // 在缓冲区数据的末尾,有一个数据结构skb_shared_info,它保存了数据块的附加信息。结构看下面~~~~
                 int first_len = skb_pagelen(skb);
                 int truesizes = 0;
                 // 判断第一个包长度是否符合一些限制(包括mtu,mf位等一些限制). 第一个数据包单独拿出来检测,是因为一些域是第一个包所独有的信息(比如IP_MF要为1)。这里由于这个mtu是不包括hlen的mtu,因此 需要减去一个hlen。
                 if (first_len - hlen > mtu ||
                     ((first_len - hlen) & 7) ||
                     (iph->frag_off & htons(IP_MF|IP_OFFSET)) ||
                     skb_cloned(skb))
                         goto slow_path;     // 下面去具体分片
                 // 下面变量这链表中剩余的frag
                 for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
                         /* Correct geometry. */
                         if (frag->len > mtu ||
                             ((frag->len & 7) && frag->next) ||
                             skb_headroom(frag) < hlen)
                             goto slow_path; // 下面去具体分片
 
                         /* Partially cloned skb? */  // 是否共享,不共享怎么不可以
                         if (skb_shared(frag))
                                 goto slow_path;    // 下面去具体分片
 
                         BUG_ON(frag->sk);
                         if (skb->sk) {
                                 sock_hold(skb->sk);  // 
                                 frag->sk = skb->sk;
                                 frag->destructor = sock_wfree;
                                 truesizes += frag->truesize;
                         }
                 }
 
                 /* Everything is OK. Generate! */
 
                 err = 0;
                 offset = 0;
                 frag = skb_shinfo(skb)->frag_list;  // 得到frag_list列表头
                 skb_shinfo(skb)->frag_list = NULL;
                 skb->data_len = first_len - skb_headlen(skb); // 获得数据长度
                 skb->truesize -= truesizes;
                 skb->len = first_len;
                 iph->tot_len = htons(first_len);
                 iph->frag_off = htons(IP_MF);    // 设置MF标识位
                 ip_send_check(iph);   // 检测头部
                      
                 for (;;) {    // 下面循环发送数据
                         /* Prepare header of the next frame,
                          * before previous one went down. */
                         if (frag) {
                                 frag->ip_summed = CHECKSUM_NONE;   // 无需检验(发送数据了已经)
                                 skb_reset_transport_header(frag);  // 定位传输层头部
                                 __skb_push(frag, hlen);            // 在 frag 中预留hlen空间( 因为每一个分片中也必须有ip头部才OK!!!!!!!! )
                                 skb_reset_network_header(frag);    // 定位网络层头部
                                 memcpy(skb_network_header(frag), iph, hlen); // 将ip头拷贝到新的分片中~~~这样才能达到最终的目的地~~~
                                 iph = ip_hdr(frag);                // 获得新的分片中的头
                                 iph->tot_len = htons(frag->len);   // 设置len
                                 ip_copy_metadata(frag, skb);       // 将skb的一些属性传递给分片
                                 if (offset == 0)
                                         ip_options_fragment(frag); // 偏移为0,表示为第一个分片包, 一般第一个分片包会加入一些选项
                                 offset += skb->len - hlen;         // 计算下一个分片offset
                                 iph->frag_off = htons(offset>>3);  // 因为ip包中的offset一位表示一个字节,因此要右移3位,扩大8倍
                                 if (frag->next != NULL)            //如果不是最后一个分片,设置MF位为1,说明后面还有分片
                                         iph->frag_off |= htons(IP_MF);
                                 /* Ready, complete checksum */     // 
                                 ip_send_check(iph);
                         }
  
                         err = output(skb);    // 这个函数我们上面可以知道其实就是ip_finish_output2( 下面将会具体来说~~~~~~ )
 
                         if (!err)
                                 IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);
                         if (err || !frag)
                                 break;
 
                         skb = frag;        // 将新的分片给skb( 相当于下一次发送skb就是咯~ )
                         frag = skb->next;  // frag指向下一个
                         skb->next = NULL;
                 }  // for结束
 
                 if (err == 0) {
                         IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
                         return 0;
                 }
 
                 while (frag) {  // 释放内存
                         skb = frag->next;
                         kfree_skb(frag);
                         frag = skb;
                 }
                 IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
                 return err;   // 返回
         }
 
 slow_path:
         left = skb->len - hlen;         /* Space per frame */ // 原始长度
         ptr = raw + hlen;               /* Where to start from */ // 起始位置
 
         /* for bridged IP traffic encapsulated inside f.e. a vlan header,
          * we need to make room for the encapsulating header
          */
         pad = nf_bridge_pad(skb);  //  处理桥接、VLAN、PPPOE相关MTU
         ll_rs = LL_RESERVED_SPACE_EXTRA(rt->u.dst.dev, pad);
         mtu -= pad;
 
         /*
          *      Fragment the datagram.
          */
 
         offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3; // 取出前13位即偏移位,并乘8算出总字节数即:该包的偏移字节数
         not_last_frag = iph->frag_off & htons(IP_MF);     // 取出第十四位MF判断是不是最后一个分片
 
         /*
          *      Keep copying data until we run out.
          */
 
         while (left > 0) {   // 下面进行循环分片,每一次分片创建一个skb
                 len = left;
                 /* IF: it doesn't fit, use 'mtu' - the data space left */
                 if (len > mtu)    // len > mtu,那么一次最大长度只能是MTU
                         len = mtu;
                 /* IF: we are not sending upto and including the packet end
                    then align the next start on an eight byte boundary */
                 if (len < left) {
                         len &= ~7; // 这个需要理解一下:对于分片,只要不是最后一个,那么都必须是8字节的倍数!!!
                 }
                 /*
                  *      Allocate buffer.
                  */
 
                 if ((skb2 = alloc_skb(len+hlen+ll_rs, GFP_ATOMIC)) == NULL) {   // 分配一个skb分片
                         NETDEBUG(KERN_INFO "IP: frag: no memory for new fragment!\n");
                         err = -ENOMEM;
                         goto fail;
                 }
 
                 /*
                  *      Set up data on packet
                  */
                 // 下面就是构造这个分片数据包(和上面差不多)
                 ip_copy_metadata(skb2, skb);   // copy 原始skb的属性
                 skb_reserve(skb2, ll_rs);      
                 skb_put(skb2, len + hlen);     // 预留头空间
                 skb_reset_network_header(skb2);// 网络层头
                 skb2->transport_header = skb2->network_header + hlen; // 传输层头
 
                 /*
                  *      Charge the memory for the fragment to any owner
                  *      it might possess
                  */
 
                 if (skb->sk)  // 将每一个分片的ip包都关联到源包的socket
                         skb_set_owner_w(skb2, skb->sk);
 
                 /*
                  *      Copy the packet header into the new buffer.
                  */
 
                 skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen); // 将skb的头copy到新的分片包中
 
                 /*
                  *      Copy a block of the IP datagram.
                  */
                 if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len)) // 拷贝数据部分
                         BUG();
                 left -= len;    // copy一部分数据,减少一部分
 
                 /*
                  *      Fill in the new header fields.
                  */
                 iph = ip_hdr(skb2);   // 头部
                 iph->frag_off = htons((offset >> 3));  //  因为ip包中的offset一位表示一个字节,因此要右移3位,扩大8倍
 
                 /* ANK: dirty, but effective trick. Upgrade options only if
                  * the segment to be fragmented was THE FIRST (otherwise,
                  * options are already fixed) and make it ONCE
                  * on the initial skb, so that all the following fragments
                  * will inherit fixed options.
                  */
                 if (offset == 0)   // 第一个分片包
                         ip_options_fragment(skb);
 
                 /*
                  *      Added AC : If we are fragmenting a fragment that's not the
                  *                 last fragment then keep MF on each bit
                  */
                 if (left > 0 || not_last_frag)
                         iph->frag_off |= htons(IP_MF);
                 ptr += len;
                 offset += len;
 
                 /*
                  *      Put this fragment into the sending queue.
                  */
                 iph->tot_len = htons(len + hlen);
 
                 ip_send_check(iph);   // 检测合法性
 
                 err = output(skb2);   // 发送出去
                 if (err)
                         goto fail;
 
                 IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);
         }
         kfree_skb(skb);
         IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
         return err;
 
 fail:
         kfree_skb(skb);
         IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
         return err;
}

看一下skb_share_info:

/* This data is invariant across clones and lives at
 * the end of the header data, ie. at skb->end.
 */
struct skb_shared_info {
         atomic_t        dataref;   // 数据块的“用户”数
         unsigned short  nr_frags;  // 几个frags
         unsigned short  gso_size;  // gso size
         /* Warning: this field is not always filled in (UFO)! */ 
         unsigned short  gso_segs; 
         unsigned short  gso_type;
         __be32          ip6_frag_id; // 分片编号
         struct sk_buff  *frag_list;// 存放分片数据包链表
         skb_frag_t      frags[MAX_SKB_FRAGS];
};

OK,终于分片结束了,现在该看看怎么去发送了吧,output函数,其实就是:ip_finish_output2函数:

static inline int ip_finish_output2(struct sk_buff *skb)
{
         struct dst_entry *dst = skb->dst;
         struct rtable *rt = (struct rtable *)dst;
         struct net_device *dev = dst->dev;
         unsigned int hh_len = LL_RESERVED_SPACE(dev);
 
         if (rt->rt_type == RTN_MULTICAST)   // 多播
                 IP_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS);
         else if (rt->rt_type == RTN_BROADCAST) // 广播
                 IP_INC_STATS(IPSTATS_MIB_OUTBCASTPKTS);
 
         /* 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 == NULL) {
                         kfree_skb(skb);
                         return -ENOMEM;
                 }
                 if (skb->sk)
                         skb_set_owner_w(skb2, skb->sk);
                 kfree_skb(skb);
                 skb = skb2;
         }
 
         if (dst->hh)
                 return neigh_hh_output(dst->hh, skb);    // 这个函数很重要!不过在此处先不说了,涉及到ARP内容!!!!!!!后期再说吧~
         else if (dst->neighbour)
                 return dst->neighbour->output(skb);  // 这个函数很重要!不过在此处先不说了,涉及到ARP内容!!!!!!!后期再说吧~
 
         if (net_ratelimit())
                 printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
         kfree_skb(skb);
         return -EINVAL;
 }

至此,单播情况结束!


对于多播情况:看看函数:ip_mc_output

int ip_mc_output(struct sk_buff *skb)
{
         struct sock *sk = skb->sk;
         struct rtable *rt = (struct rtable*)skb->dst;
         struct net_device *dev = rt->u.dst.dev;
 
         /*
          *      If the indicated interface is up and running, send the packet.
          */
         IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
 
         skb->dev = dev;
         skb->protocol = htons(ETH_P_IP);
 
         /*
          *      Multicasts are looped back for other local users
          */
 
         if (rt->rt_flags&RTCF_MULTICAST) {
                 if ((!sk || inet_sk(sk)->mc_loop)
 #ifdef CONFIG_IP_MROUTE
                 /* Small optimization: do not loopback not local frames,
                    which returned after forwarding; they will be  dropped
                    by ip_mr_input in any case.
                    Note, that local frames are looped back to be delivered
                    to local recipients.
 
                    This check is duplicated in ip_mr_input at the moment.
                  */
                     && ((rt->rt_flags&RTCF_LOCAL) || !(IPCB(skb)->flags&IPSKB_FORWARDED))
 #endif
                 ) {
                         struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
                         if (newskb)
                                 NF_HOOK(PF_INET, NF_INET_POST_ROUTING, newskb,
                                         NULL, newskb->dev,
                                         ip_dev_loopback_xmit);
                 }
 
                 /* Multicasts with ttl 0 must not go beyond the host */
 
                 if (ip_hdr(skb)->ttl == 0) {
                         kfree_skb(skb);
                         return 0;
                 }
         }
 
         if (rt->rt_flags&RTCF_BROADCAST) {
                 struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
                 if (newskb)
                         NF_HOOK(PF_INET, NF_INET_POST_ROUTING, newskb, NULL,
                                 newskb->dev, ip_dev_loopback_xmit);
         }
 
         return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, skb->dev,   // 其实最终的执行函数还是这个ip_finish_output函数~~~~~~~~
                             ip_finish_output,
                             !(IPCB(skb)->flags & IPSKB_REROUTED));
 }


上面讲完了从本地转发数据情况,即从ip_forward之后的情况!

下面开始说将数据包往上一层传递的情况!!!!即 ip_local_deliver函数!

int ip_local_deliver(struct sk_buff *skb)   // 收集IP分片,然后调用ip_local_deliver_finish将一个完整的数据包传送给上层协议
{
         /*
          *      Reassemble IP fragments.
          */
 
         if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) { // 根据frag_off判断如果这个数据包是不是分片,MF判断是不是最后一个,还有偏移OFFSET
                 if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))    // 组合数据包~~~~~~
                         return 0;
         }
 
         return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
                        ip_local_deliver_finish);      // 实际的处理函数在这里呢~~~~~~~~~~~~~~~~~~~~
}

下面先看看ip_defrag函数:

/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
         struct ipq *qp;     // 保存分片的队列
         struct net *net;
 
         IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);
 
         net = skb->dev ? skb->dev->nd_net : skb->dst->dev->nd_net;
         /* Start by cleaning up the memory. */
         if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh) // 首先检查所有IP分片所消耗的内存是否大于系统允许的最高阀值,如果是,则调用ip_evictor()丢弃未完全到达的IP分片,从最旧的分片开始释放。
                 ip_evictor(net);
 
         /* Lookup (or create) queue header */
         if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) {// 如果该分片是数据报的第一个分片,则创建一个新的队列来重排分片,否则返回所属的分片队列。

                 int ret;
 
                 spin_lock(&qp->q.lock);
 
                 ret = ip_frag_queue(qp, skb);   //将该分片加入到队列中,重组分片队列,如果所有的包都收到了,则该函数负责重组IP包
                 spin_unlock(&qp->q.lock); 

                 ipq_put(qp);
                 return ret;
         }
 
         IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
         kfree_skb(skb);
         return -ENOMEM;
}

看ip_find函数:

static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)
{
         struct inet_frag_queue *q;
         struct ip4_create_arg arg;
         unsigned int hash;
 
         arg.iph = iph;
         arg.user = user;
         hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);   // 获取计算得到的hash值
         // 注意这个hash还有一个随机值ip4_frags.rnd,,其初始化为(u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));

         q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);  //  若存在该分片所属的分片队列,则返回这个队列,否则创建一个新的队列 
         if (q == NULL)                                                 // 下面看这个函数
                 goto out_nomem;                                        // 注意ip4_frags是全局变量

         return container_of(q, struct ipq, q);
 
 out_nomem:
         LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
         return NULL;
}

看看inet_frag_find函数:

struct inet_frag_queue *inet_frag_find(struct netns_frags *nf,
                 struct inet_frags *f, void *key, unsigned int hash)
{
         struct inet_frag_queue *q;
         struct hlist_node *n;
 
         read_lock(&f->lock);
         hlist_for_each_entry(q, n, &f->hash[hash], list) {  // 下面在全局变量f中寻找存不存在这个队列
                 if (q->net == nf && f->match(q, key)) {   // 队列匹配
                         atomic_inc(&q->refcnt);
                         read_unlock(&f->lock);
                         return q;   // 找到返回队列
                 }
         }
         read_unlock(&f->lock);
 
         return inet_frag_create(nf, f, key, hash);   /// 没找到就创建一个新的队列(分片是第一个分片)
}

OK,现在返回到ip_defrag中,看看下面的函数: ip_frag_queue,怎么样重新将所有分片连接起来!

对于这个函数不想多说了,基本就是当所有的分片都达到的时候,将分片请按照offset位置连接起来~~~~~~形成一个整体的skb~~~~~~~

注意到这个qp队列其实是全局的就OK了,由 ip4_frags 管理~~~~~~~

/* Add new segment to existing queue. */
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
         struct sk_buff *prev, *next;
         struct net_device *dev;
         int flags, offset;
         int ihl, end;
         int err = -ENOENT;
 
         if (qp->q.last_in & COMPLETE)
                 goto err;
 
         if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
             unlikely(ip_frag_too_far(qp)) &&
             unlikely(err = ip_frag_reinit(qp))) {
                 ipq_kill(qp);
                 goto err;
         }
 
         offset = ntohs(ip_hdr(skb)->frag_off);
         flags = offset & ~IP_OFFSET;
         offset &= IP_OFFSET;
         offset <<= 3;           /* offset is in 8-byte chunks */
         ihl = ip_hdrlen(skb);
 
         /* Determine the position of this fragment. */
         end = offset + skb->len - ihl;
         err = -EINVAL;
 
         /* Is this the final fragment? */
         if ((flags & IP_MF) == 0) {
                 /* If we already have some bits beyond end
                  * or have different end, the segment is corrrupted.
                  */
                 if (end < qp->q.len ||
                     ((qp->q.last_in & LAST_IN) && end != qp->q.len))
                         goto err;
                 qp->q.last_in |= LAST_IN;
                 qp->q.len = end;
         } else {
                 if (end&7) {
                         end &= ~7;
                         if (skb->ip_summed != CHECKSUM_UNNECESSARY)
                                 skb->ip_summed = CHECKSUM_NONE;
                 }
                 if (end > qp->q.len) {
                         /* Some bits beyond end -> corruption. */
                         if (qp->q.last_in & LAST_IN)
                                 goto err;
                         qp->q.len = end;
                 }
         }
         if (end == offset)
                 goto err;
 
         err = -ENOMEM;
         if (pskb_pull(skb, ihl) == NULL)
                 goto err;
 
         err = pskb_trim_rcsum(skb, end - offset);
         if (err)
                 goto err;
 
         /* Find out which fragments are in front and at the back of us
          * in the chain of fragments so far.  We must know where to put
          * this fragment, right?
          */
         prev = NULL;
         for (next = qp->q.fragments; next != NULL; next = next->next) {
                 if (FRAG_CB(next)->offset >= offset)
                         break;  /* bingo! */
                 prev = next;
         }
 
         /* We found where to put this one.  Check for overlap with
          * preceding fragment, and, if needed, align things so that
          * any overlaps are eliminated.
          */
         if (prev) {
                 int i = (FRAG_CB(prev)->offset + prev->len) - offset;
 
                 if (i > 0) {
                         offset += i;
                         err = -EINVAL;
                         if (end <= offset)
                                 goto err;
                         err = -ENOMEM;
                         if (!pskb_pull(skb, i))
                                 goto err;
                         if (skb->ip_summed != CHECKSUM_UNNECESSARY)
                                 skb->ip_summed = CHECKSUM_NONE;
                 }
         }
 
         err = -ENOMEM;
 
         while (next && FRAG_CB(next)->offset < end) {
                 int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
 
                 if (i < next->len) {
                         /* Eat head of the next overlapped fragment
                          * and leave the loop. The next ones cannot overlap.
                          */
                         if (!pskb_pull(next, i))
                                 goto err;
                         FRAG_CB(next)->offset += i;
                         qp->q.meat -= i;
                         if (next->ip_summed != CHECKSUM_UNNECESSARY)
                                 next->ip_summed = CHECKSUM_NONE;
                         break;
                 } else {
                         struct sk_buff *free_it = next;
 
                         /* Old fragment is completely overridden with
                          * new one drop it.
                          */
                         next = next->next;
 
                         if (prev)
                                 prev->next = next;
                         else
                                 qp->q.fragments = next;
 
                         qp->q.meat -= free_it->len;
                         frag_kfree_skb(qp->q.net, free_it, NULL);
                 }
         }
 
         FRAG_CB(skb)->offset = offset;
 
         /* Insert this fragment in the chain of fragments. */
         skb->next = next;
         if (prev)
                 prev->next = skb;
         else
                 qp->q.fragments = skb;
 
         dev = skb->dev;
         if (dev) {
                 qp->iif = dev->ifindex;
                 skb->dev = NULL;
        }
         qp->q.stamp = skb->tstamp;
         qp->q.meat += skb->len;
         atomic_add(skb->truesize, &qp->q.net->mem);
         if (offset == 0)
                 qp->q.last_in |= FIRST_IN;
 
         if (qp->q.last_in == (FIRST_IN | LAST_IN) && qp->q.meat == qp->q.len)
                 return ip_frag_reasm(qp, prev, dev);
 
         write_lock(&ip4_frags.lock);
         list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
         write_unlock(&ip4_frags.lock);
         return -EINPROGRESS;
 
 err:
         kfree_skb(skb);
         return err;
}

OK,现在ip_defrag函数解释完成了,那么回到ip_local_deliver函数中,看接下来的ip_local_deliver_finish函数:

static int ip_local_deliver_finish(struct sk_buff *skb)
{
         __skb_pull(skb, ip_hdrlen(skb));   // 指向ip头后面,也就是跳过ip头
 
         /* Point into the IP datagram, just past the header. */
         skb_reset_transport_header(skb);   // 设置传输层头部位置
 
         rcu_read_lock();
         {
                 int protocol = ip_hdr(skb)->protocol;    // 获得协议类型
                 int hash, raw;
                 struct net_protocol *ipprot;
 
         resubmit:
                 raw = raw_local_deliver(skb, protocol);  // 判断是不是原始套接字
 
                 hash = protocol & (MAX_INET_PROTOS - 1); // 获得当前协议类型
                 if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) { // 查找注册的4层协议处理结构(具体的注册情况看下面~~~~~~~~~~~)
                         int ret;                            // 注意inet_protos是全局数组,保存所有的注册的协议,类型是: struct net_protocol 看看下面
 
                         if (!ipprot->no_policy) {   // 启用了安全策略,则交给IPSec(IP security)
                                 if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                                         kfree_skb(skb);
                                         goto out;
                                 }
                                 nf_reset(skb);
                         }
                         ret = ipprot->handler(skb);    // 4层处理函数!!!注意这是在哪里注册的~看看下面的 ----> 4层的协议注册情况
                         if (ret < 0) {                 // 例如对于tcp来说就是 tcp_v4_rcv
                                 protocol = -ret;
                                 goto resubmit;
                         }
                        IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
                 } else {     // 如果不存在这个协议
                         if (!raw) {   // 无原始套接字,提交给IPSec
                                 if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                                         IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
                                         icmp_send(skb, ICMP_DEST_UNREACH,     // 发送icmp回送消息
                                                   ICMP_PROT_UNREACH, 0);
                                 }
                         } else
                                 IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
                         kfree_skb(skb);
                 }
         }
out:
         rcu_read_unlock();
 
         return 0;
}

看看这个协议结构体:

/* This is used to register protocols. */
 struct net_protocol {
         int                     (*handler)(struct sk_buff *skb);                // 这个协议怎么处理数据包skb
         void                    (*err_handler)(struct sk_buff *skb, u32 info);  // 错误处理函数
         int                     (*gso_send_check)(struct sk_buff *skb);         
         struct sk_buff         *(*gso_segment)(struct sk_buff *skb,
                                                int features);
         int                     no_policy;
 };


层的协议注册情况:都注册为net_protocol结构,并hash到inet_protos表中进行统一管理

#ifdef CONFIG_IP_MULTICAST
static const struct net_protocol igmp_protocol = {
	.handler =	igmp_rcv,
	.netns_ok =	1,
};
#endif

static const struct net_protocol tcp_protocol = {
	.handler =	tcp_v4_rcv,
	.err_handler =	tcp_v4_err,
	.gso_send_check = tcp_v4_gso_send_check,
	.gso_segment =	tcp_tso_segment,
	.gro_receive =	tcp4_gro_receive,
	.gro_complete =	tcp4_gro_complete,
	.no_policy =	1,
	.netns_ok =	1,
};

static const struct net_protocol udp_protocol = {
	.handler =	udp_rcv,
	.err_handler =	udp_err,
	.gso_send_check = udp4_ufo_send_check,
	.gso_segment = udp4_ufo_fragment,
	.no_policy =	1,
	.netns_ok =	1,
};

static const struct net_protocol icmp_protocol = {
	.handler =	icmp_rcv,
	.no_policy =	1,
	.netns_ok =	1,
};

对于TCP协议来说,那么下面继续执行这个函数:tcp_v4_rcv....


OK,至此,ip层处理结束~~~~~~~~~~~~~~~~~















  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Linux 内核裁剪为 RT-Linux 内核需要进行以下步骤: 1. 下载 RT-Linux内核补丁。RT-Linux内核补丁包括了实时调度程序和其他的实时功能。你可以从 RT-Linux 官方网站下载最新的内核补丁。 2. 下载 Linux 内核源代码。你可以从 Linux 的官方网站或其他镜像站点下载最新的源代码。 3. 解压缩 Linux 内核源代码。使用以下命令解压缩源代码: ``` tar xzf linux-x.y.z.tar.gz ``` 其中,x.y.z 是你下载的 Linux 内核版本号。 4. 进入 Linux 内核源代码的目录。 ``` cd linux-x.y.z ``` 5. 应用 RT-Linux内核补丁。使用以下命令将 RT-Linux内核补丁应用到 Linux 内核源代码中: ``` patch -p1 < /path/to/rt-linux-patch-x.y.z.diff ``` 其中,/path/to/rt-linux-patch-x.y.z.diff 是你下载的 RT-Linux 内核补丁的路径。 6. 配置内核。使用以下命令进入内核配置界面: ``` make menuconfig ``` 在内核配置界面中,选择 “实时补丁” 和其他需要的实时功能,然后保存并退出。 7. 编译内核。使用以下命令编译内核: ``` make ``` 8. 安装内核。使用以下命令安装编译好的内核: ``` make install ``` 9. 配置引导程序。如果你使用 GRUB 引导程序,编辑 /etc/default/grub 文件,将 GRUB_DEFAULT 设置为新内核的名称,然后更新 GRUB 配置文件: ``` update-grub ``` 10. 重启系统。使用以下命令重启系统: ``` reboot ``` 完成上述步骤后,你的 Linux 内核就已经裁剪成了 RT-Linux 内核

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值