Chinaunix首页 | 论坛 | 博客
  • 博客访问: 66108
  • 博文数量: 28
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2016-12-22 11:48
文章分类
文章存档

2018年(5)

2017年(23)

我的朋友

分类: LINUX

2018-08-29 14:20:02


1, ip包的转发

* ip包转发流程函数调用关系

ip_rcv_finish()
    ->skb->dst->input()  
    ->ip_forward()  //若是非本地包
   -> ip_forward_finish()
      -> ip_forward_options()
      -> dst_output()  // 发送ip数据包
         -> ip_output()
         -> ip_output_finish()
   ->ip_local_deliver()    // 本地数据包
             ->ip_defrag()    // 碎片重组
             ->ip_local_deliver_finish()  //调用第传输层协议处理数据包:tcp_v4_rcv等。


* ip包转发的实现
/*
  * ip_forward函数的主要流程如下:
  * (1) 检查LRO是否被设置,若设置不能被转发,直接丢弃该数据包
  * (2) 检查ipsec策略,若没有通过丢弃该数据包
  * (3) 检查是否设置了Router Alert选项,若设置了调用ip_call_ra_chain函数进行处理,若处理成功直接返回NET_RX_SUCCESS,该函数的流程结束。
  * (4) 检查ttl的值,若!=1 丢弃该数据包
  * (5) 检查下一跳的值,若有更优的下一跳,则调用ip_rt_send_redirect发送一个ICMP的REDIRECT消息
  * (6) 进入netfilter的链,进行规则检测
  * (7) 最后,调用ip_forward_finish进行后半部分的处理
  * 
  */
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);

    //  接收到的包不能被转发,因为LRO(Large Receive Offload)被设置。直接丢弃该包。
    if (skb_warn_if_lro(skb))
        goto drop;

    // 检查ipsec策略
    if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
        goto drop;

    // 若设置了Router Alert选项,则调用ip_call_ra_chain函数进行处理
    // 参见《ip选项处理》一节
    if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
        return NET_RX_SUCCESS;
    // 若该数据包不是自己的包,则丢弃该包
    if (skb->pkt_type != PACKET_HOST)
        goto drop;

    // 我们处理的是ip层的包,而校验和是tcp层的事,所以这里我们只需要把校验和设置成空
    skb_forward_csum(skb);

    // 若ip包的ttl<=1,必须要丢弃该数据包,并且发送一个icmp信息给源地址。
    // icmp消息类型是:ICMP_TIME_EXCEEDED,编号是:ICMP_EXC_TTL
    // 注意此时ttl还没有被减1,因为此时可能有其他的子系统还在共享该数据包,例如抓包器等。
    /*
     *  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)
        goto too_many_hops;

    // 检查是否有ipsec策略要转发该包
    if (!xfrm4_route_forward(skb))
        goto drop; 
    // 获取该数据包的rtable结构指针,该结构中包含所有的转发包需要的信息,包括下一跳的主机rt_gateway
    rt = skb->rtable;

    // 若ip头包含一个Strict Source Route选项,且包的下一跳和路由表中的不匹配,则丢弃该数据包
    if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
        goto sr_failed;

    // 若数据包大于目的ip的mtu大小,且设置了IP_DF标志位,则丢弃该数据包
    // 并发送一个icmp错误信息,类型是:ICMP_DEST_UNREACH,编码是:ICMP_FRAG_NEEDED
    if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&
             (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {
        IP_INC_STATS(dev_net(rt->u.dst.dev), IPSTATS_MIB_FRAGFAILS);
        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
              htonl(dst_mtu(&rt->u.dst)));
        goto drop;
    }

    // 因为我们要修改数据包的内容了,此时需要检查数据包是否被共享
    // 若是共享数据包,就调用skb_cow复制一个副本。
    /* We are about to mangle packet. Copy it! */
    if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
        goto drop;
    // 获取skb中的ip头指针
    iph = ip_hdr(skb);

    // 现在把skb中的ttl字段减1,此时也要共享ip的校验和
    /* Decrease ttl after skb cow done */
    ip_decrease_ttl(iph);

    /*
     *  We now generate an ICMP HOST REDIRECT giving the route
     *  we calculated.
     */
    // 若存在比原来包中更优的下一跳,则给源发送一个ICMP的REDIRECT消息
    if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb->sp)
        ip_rt_send_redirect(skb);

    // 用ip头的tos字段设置skb->priority优先级,用于以后的流量控制(Qos)
    skb->priority = rt_tos2priority(iph->tos);

    // 处理完所有的事情后,进入netfilter的链,若没有netfilter规则,直接进入ip_forward_finish函数
    return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
               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... */
    IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_INHDRERRORS);
    icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
drop:
    kfree_skb(skb);
    return NET_RX_DROP;
}

* 包转发下半部分
// 到这里说明ip包的所有检查已经完成,可以转发该数据包了
static int ip_forward_finish(struct sk_buff *skb)
{
    // 获取ip的选项
    struct ip_options * opt = &(IPCB(skb)->opt);
    // 添加ip的统计数据
    IP_INC_STATS_BH(dev_net(skb->dst->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);

    // 处理forward相关的选项,见ip选项处理一节
    if (unlikely(opt->optlen))
        ip_forward_options(skb);

    // 调用dst_output
    return dst_output(skb);
}

// 就是调用skb中的dst指针的output函数进行发送
// 若是单播地址,则会初始化为ip_output
// 若是多播地址,则会初始化为ip_mc_output
/* Output packet to network from transport.  */
static inline int dst_output(struct sk_buff *skb)
{
    return skb->dst->output(skb);
}



2, ip包本地传送

/*
 *  Deliver IP Packets to the higher protocol layers.
 */
int ip_local_deliver(struct sk_buff *skb)
{
    /*  
     *  Reassemble IP fragments.
     */
    // 若包是分片包,需要重组分片包,分片包的重组,有另外的章节描述
    if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
        if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
            return 0;
    }   

    // 调用ip_local_deliver_finish函数进行上层处理
    return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
               ip_local_deliver_finish);
}


* 本地包处理下半部分
本地Ip包处理的下半部分,主要调用第4层协议的相应处理函数,处理数据包。
static int ip_local_deliver_finish(struct sk_buff *skb)
{
    struct net *net = dev_net(skb->dev);

    // 调整skb数据包的结构和长度
    __skb_pull(skb, ip_hdrlen(skb));

    // 让传输层的头指针,指向skb->data的指针
    /* 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 socket发送的,需要做相应的处理,clone数据包
        raw = raw_local_deliver(skb, protocol);
        // 计算传输层协议处理结构在inet_protos数组hash表中的位置
        hash = protocol & (MAX_INET_PROTOS - 1);
        // 获取传输层协议处理指针
        ipprot = rcu_dereference(inet_protos[hash]);
        // 若获取到了对应传输层的处理结构
        if (ipprot != NULL && (net == &init_net || ipprot->netns_ok)) {
            int ret;
        
            // 检查ipsec策略
            if (!ipprot->no_policy) {
                if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    kfree_skb(skb);
                    goto out;
                }
                nf_reset(skb);
            }
            // 调用传输层处理函数进行处理。
            // tcp的是tcp_v4_rcv,udp是udp_rcv...
            ret = ipprot->handler(skb);
            // 处理数据包失败,再次尝试
            if (ret < 0) {
                protocol = -ret;
                goto resubmit;
            }
            // 添加数据包处理统计信息
            IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
        } else { // 若没有找到相应传输层的处理函数
            if (!raw) { // 不是raw socket的数据包,检查ipsec安全策略
                if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);
                    icmp_send(skb, ICMP_DEST_UNREACH,
                          ICMP_PROT_UNREACH, 0);
                }
            } else
                IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
            kfree_skb(skb);
        }
    }
 out:
    rcu_read_unlock();

    return 0;
}


说明:基于 linux kernel 版本:  2.6.27

阅读(2633) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~