linux网络协议栈分析笔记6-IP层的处理2

上章说到ip_rcv_finish最后会有两个选择:
1) ip_local_deliver
2)ip_forward

现在我们看下ip_forward()
->ip_forward()
     ->struct ip_options * opt     = &(IPCB(skb)->opt);  option中保存的是skb的一些ip头中的options信息
     ->if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))  如果router alert option被设置了,则立即交由ip_call_ra_chain处理数据包
          return NET_RX_SUCCESS;
     ->if (skb->pkt_type != PACKET_HOST)
          goto drop;
     ->if (ip_hdr(skb)->ttl <= 1)   检查TTL
          goto too_many_hops;
     ->rt = skb_rtable(skb);  获得路由信息
     ->if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)   设置了严格ip源站选路选项(必须按发送者指定的路线走)
          goto sr_failed;
     ->if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))因为此时可能对skb做处理,所以要copy一个数据包
          goto drop;
     ->ip_decrease_ttl(iph); TTL-1
     ->if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb)) 
          ip_rt_send_redirect(skb);   如果没设置源站选路选项,则如果有更好的路线,通知源端,即ip报文的重定向
     ->NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
                  ip_forward_finish );
以上主要处理路由和报文ip头相关字段信息,最终进 ip_forward_finish

static int  ip_forward_finish (struct sk_buff *skb)
{
     struct ip_options * opt     = &(IPCB(skb)->opt);

     IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_OUTFORWDATAGRAMS); 记录MIB
     
     if (unlikely(opt->optlen))
          ip_forward_options(skb);  继续处理ip头的option选项

     return dst_output(skb);   进入发送流程
}  
看来IP层主要都是围绕路由在选择后续处理动作,我们后续对路由进行主要分析,接着看前一章提到过的一个重要处理:
ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER)   IP分片报文的重组
(1)当内核接收到本地的IP包,在传递给上层协议处理之前,先进行碎片重组。IP包片段之间的标识号(id)是相同的。当IP包片偏量(frag_off)第 14位(IP_MF)为1时,表示该IP包有后继片段。片偏量的低13位则为该片段在完整数据包中的偏移量,以8字节为单位。当IP_MF位为0时,表示IP包是最后一块碎片。
(2)碎片重组由重组队列完成,每一重组队列对应于(daddr, saddr, protocol, id)构成的键值,它们存在于ipq结构构成的散列链之中。重组队列将IP包按照将片段偏量的顺序进行排列,当所有的片段都到齐后,就可以将队列中的包碎片按顺序拼合成一个完整的IP包
(3)如果30秒后重组队列内包未到齐,则重组过程失败,重组队列被释放,同时向发送方以ICMP协议通知失败信息。重组队列的内存消耗不得大于256k (sysctl_ipfrag_high_thresh),否则将会调用(ip_evictor)释放每支散列尾端的重组队列
->ip_defrag()
     ->if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)  不得超过规定内存空间大小
               ip_evictor(net);
     ->qp =   ip_find(net, ip_hdr(skb), user))  寻找这片报文的iqp头
     -> ip_frag_queue(qp, skb);  将这片SKB插入hash队列中
          ->if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
                                       qp->q.meat == qp->q.len) 如果是第一个碎片包或者最后一个碎片包,并且偏移长度总和等于碎片长度总和
                         return   ip_frag_reasm(qp, prev, dev);   所有分片已到  进行重组

上图主要用到的数据结构:
分片缓存结构
struct ipq {
       struct inet_frag_queue q;        分片队列结构

     u32          user;
     __be32          saddr;         hash因子 :   源ip,目的ip 协议号  报文id
     __be32          daddr;
     __be16          id;
     u8          protocol;

     int             iif;
     unsigned int    rid;
     struct inet_peer *peer;
};
分片队列结构
struct inet_frag_queue {
       struct hlist_node     list;             哈希结点结构
     struct netns_frags     *net;
     struct list_head     lru_list;   /* lru list member */
     spinlock_t          lock;
     atomic_t          refcnt;
       struct timer_list     timer;      /* when will this queue expire? */        分片缓存的老化定时器
      struct sk_buff          *fragments; /* list of received fragments */      收到分片的链
     ktime_t               stamp;
     int               len;        /* total length of orig datagram */
     int               meat;
     __u8               last_in;    /* first/last segment arrived? */

#define INET_FRAG_COMPLETE     4     目前分片缓存的状态
#define INET_FRAG_FIRST_IN     2 
#define INET_FRAG_LAST_IN     1

};

分片总队列:
struct inet_frags { 
      struct hlist_head     hash[INETFRAGS_HASHSZ];         #define INETFRAGS_HASHSZ          64   哈希桶
     rwlock_t          lock;
     u32               rnd;            哈希随机因子, 定时变更,用来抗攻击
     int               qsize;
     int               secret_interval;
     struct timer_list     secret_timer;
操作函数:
     unsigned int          (*hashfn)(struct inet_frag_queue *);
     void               (*constructor)(struct inet_frag_queue *q,
                              void *arg);
     void               (*destructor)(struct inet_frag_queue *);
     void               (*skb_free)(struct sk_buff *);
     int               (*match)(struct inet_frag_queue *q,
                              void *arg);
     void               (*frag_expire)(unsigned long data);
};

三个重量级函数:
ip_find:
ip_frag_queue
ip_frag_reasm
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;

     read_lock(&ip4_frags.lock);
     hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);  根据四因子,得到hash值

     q =   inet_frag_find(&net->ipv4.frags, & ip4_frags, &arg, hash);   进入hash桶查找
     if (q == NULL)
          goto out_nomem;

     return container_of(q, struct ipq, q);

out_nomem:
     LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
     return NULL;
}
ip4_frags结构的初始化
void __init   ipfrag_init(void)
{
     ip4_frags_ctl_register();
     register_pernet_subsys(&ip4_frags_ops);
     ip4_frags.hashfn =   ip4_hashfn;                 hash函数
     ip4_frags.constructor = ip4_frag_init;
     ip4_frags.destructor = ip4_frag_free;
     ip4_frags.skb_free = NULL;
     ip4_frags.qsize = sizeof(struct ipq);
     ip4_frags.match =   ip4_frag_match;         匹配函数
     ip4_frags.frag_expire =   ip_expire;          老化函数
     ip4_frags.secret_interval = 10 * 60 * HZ;
     inet_frags_init(&ip4_frags);
}
->inet_frag_find()
     -> hlist_for_each_entry(q, n, &f->hash[hash], list) {
               if (q->net == nf && f-> match(q, key)) {
               atomic_inc(&q->refcnt);
               read_unlock(&f->lock);
               return q;         找到了则返回头节点
               }
          }
     ->inet_frag_create() 否则创建分片缓存的一个空间


ip_frag_queue()  进行分片入队列
     主要进行合法性校验,报文重叠处理,排序插入等操作,此处不再详细分析

ip_frag_reasm()
取得ipq中fragments头节点的ip头长度
       ihlen = ip_hdrlen(head);

       如果把头节点的IP首部长度加上ipq结构中的碎片总长度相加,就得到了重组之
       后报文的长度
       len = ihlen + qp->len;

       if (len > 65535)
              goto out_oversize;

       头节点必须没有被克隆过
       if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
              goto out_nomem;

       碎片中第一个节点如果是分片的,需要特殊处理,这里的”分片”并不是指IP包的
       碎片,而是指skb存储结构离散分布,并不在一个连续的内存空间内

       if (skb_shinfo(head)->frag_list) {
              struct sk_buff *clone;
              int i, plen = 0;
              如果头节点是分片的,那么需要重新申请一个skb,并且把这个新的skb放到
              第一个skb end指针之后skb_shared_info结构的frag_list链表上
              if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
                     goto out_nomem;
              clone->next = head->next;
              head->next = clone;

              把head原来的分片放在新申请的skb的frag_list里面
              skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
              skb_shinfo(head)->frag_list = NULL;

              计算head中总的分片长度
              for (i=0; i<skb_shinfo(head)->nr_frags; i++)
                     plen += skb_shinfo(head)->frags[i].size;
            
           实际上最后生成了一个自身数据为0.不包含任何数据,但是这个新的的frag_list中却包含了所有的分片
              clone->len = clone->data_len = head->data_len - plen;
              head->data_len -= clone->len;
              head->len -= clone->len;
              clone->csum = 0;
              clone->ip_summed = head->ip_summed;
              atomic_add(clone->truesize, &ip_frag_mem);
       }
   
       把head以后所有的碎片都当作是head frag_list里面的分片来处理
       skb_shinfo(head)->frag_list = head->next;


       skb_push(head, head->data - skb_network_header(head));
       atomic_sub(head->truesize, &ip_frag_mem);

      协议栈的处理会通过skb_linearize()函数将head报文的frag_list链表里面的数据包都合并成一个报文,所以

将链表里面所有skb的len和data_len,以及true_size都和head中相应的值相加,最后得到了合并后数据包的长度
       for (fp=head->next; fp; fp = fp->next) {
              head->data_len += fp->len;
              head->len += fp->len;
              if (head->ip_summed != fp->ip_summed)
                     head->ip_summed = CHECKSUM_NONE;
              else if (head->ip_summed == CHECKSUM_COMPLETE)
                     head->csum = csum_add(head->csum, fp->csum);
              head->truesize += fp->truesize;
              atomic_sub(fp->truesize, &ip_frag_mem);
       }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值