免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 797 | 回复: 0
打印 上一主题 下一主题

Linux 内核中6to4隧道的处理流程 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-12-20 09:44 |只看该作者 |倒序浏览
   当IP数据包上传给IP层时,有IP层的ip_rcv()函数进行接收,这个函数的主要作用就是检查这个数据包。这个函数不会处理不属于这个主机的数据包。也就是这个包的目标MAC地址不是本机的话,该函数就不会处理,而不是L3层的IP地址。
这里我们可以看到pkt_type的类型是PACKET_OTHERHOST,说明这里是其它主机的数据包,直接进行丢弃处理。在网卡获取数据包的中断处理过程(ei_interrupt)中调用了ei_receive,而ei_receive又调用eth_type_trans,在这个过程中将包的类型设置为PACKET_OTHEHOST。

然后在ip_rcv中判断包类型是不是PACKET_OTHERHOST,如果是则丢弃之。

 

if (skb->pkt_type == PACKET_OTHERHOST)
        goto drop;

接下来是一个共享的检查,如果是共享的数据包,需要修改skb中的信息,所以需要复制一个副本,再作进一步的处理。

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

       IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);

       goto out;

    }


再下面的处理就是检查首部的长度是否够长,校检和,版本号等信息。
if (iph->ihl < 5 || iph->version != 4)
        goto inhdr_error;

    if (!pskb_may_pull(skb, iph->ihl*4))
        goto inhdr_error;
如果没有错误的话就会调ip_rcv_finiish()函数进行进一步的处理。
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
         ip_rcv_finish);
在这个函数中首先查找路由信息,
if (likely(skb->dst == NULL)) {
        int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
                     skb->dev);
然后,对IP头部中的选项信息进行了进行处理。
if (iph->ihl > 5 && ip_rcv_options(skb))
        goto drop;

    return dst_input(skb);

    最后,返回调用dst_input()函数,这个函数主机是根据路由信息调用相应的input函数,

err = skb->dst->input(skb);

    但是这个input函数可能是ip_local_deliver或者是ip_forward();

Ip_forward()函数主机是对数据包进行转发发送,ip_local_deliver()函数主要是在本机进行处理的过程。

    首先,确定接收到的包是不是分片,如果是,则要将分片重新装成一个完整的IP数据包再上传给L4层。最后调用ip_local_deliver_finish()函数。


int ip_local_deliver(struct sk_buff *skb)
if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
        skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);
        if (!skb)
            return 0;
    }
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
         ip_local_deliver_finish);
ip_local_deliver_finish

确定将这个数据包传送给L4层,L3层的头已经没有作用,先去掉L3的头部。


int ihl = skb->nh.iph->ihl*4;

    __skb_pull(skb, ihl);//将skb->data指向L4的头部。


        /* Point into the IP datagram, just past the header. */
        skb->h.raw = skb->data;// 重新设置skb->h.raw

接下来就是要处理与L4层相关的协议了,这里首先处理的是Raw IP,先查看raw_v4_htable有没有注册这个L4协议的Raw IP。
int protocol = skb->nh.iph->protocol;
        int hash;
        struct sock *raw_sk;
        struct net_protocol *ipprot;

    resubmit:
        hash = protocol & (MAX_INET_PROTOS - 1);
        raw_sk = sk_head(&raw_v4_htable[hash]);
如果有,则要执行raw_v4_input()对其进行处理。提交一个副本。
if (raw_sk && !raw_v4_input(skb, skb->nh.iph, hash))
            raw_sk = NULL;
然后,对L4层的各层协议进行相应的处理,调用不同的处理函数。
if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
            int ret;

            if (!ipprot->no_policy) {
                if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    kfree_skb(skb);
                    goto out;
                }
                nf_reset(skb);
            }
            ret = ipprot->handler(skb);//这里是调用相应的处理函数。

            if (ret < 0) {
                protocol = -ret;
                goto resubmit;
            }

调用相应的处理函数时要进行相就的注册,这里主要依据6to4隧道为例。我们可以在源码文件中sit.c,中的sit_init()函数中看到下面的注册添加函数。


if (inet_add_protocol(&sit_protocol, IPPROTO_IPV6) < 0) {
        printk(KERN_INFO "sit init: Can't add protocol\n");
        return -EAGAIN;
    }

也就是把数组中的inet_protos[]的相应位添加相应的处理函数。


int inet_add_protocol(struct net_protocol *prot, unsigned char protocol)
{
    int hash, ret;

    hash = protocol & (MAX_INET_PROTOS - 1);

    spin_lock_bh(&inet_proto_lock);
    if (inet_protos[hash]) {
        ret = -1;
    } else {
        inet_protos[hash] = prot;
        ret = 0;
    }
    spin_unlock_bh(&inet_proto_lock);

    return ret;
}

    我们可以看到这里数组的类型仍然是inet_protos,也就说仍然是IPv4中的版本号。我们这里使用的协议号为IPPROTO_IPV6也就是41。下面对收到协议号为41的协议进行处理的函数ipip6_rcv。也就是对数组sit_protocol函数指针进行赋值。当判断到的IPv48位协议字段为41时,就会调用这里的处理函数也就是ipip6_rcv(),对收到的协议数据包进行处理。这里对6to4类型的遂道而言。Linux内核为2.6.16,在Linux2.6.25以后的内核有较大的变化。其增加了ISATAP类型的遂道接口。


static struct net_protocol sit_protocol = {
    .handler    =    ipip6_rcv,
    .err_handler    =    ipip6_err,
};
其中协议的结构体如下所示:
struct net_protocol {
    int            (*handler)(struct sk_buff *skb);
    void            (*err_handler)(struct sk_buff *skb, u32 info);
    int            no_policy;
};

ipip6_rcv()函数中,首先,利用ipip6_tunnel_lookup()函数根据源目的地址查找出所属的隧道,如果查找不到会出现协议不可达和目的地址不可达的错误报文。


static int ipip6_rcv(struct sk_buff *skb)
{
    struct iphdr *iph;
    struct ip_tunnel *tunnel;

    if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
        goto out;

    iph = skb->nh.iph;

    read_lock(&ipip6_lock);
    if ((tunnel = ipip6_tunnel_lookup(iph->saddr, iph->daddr)) != NULL) {
        secpath_reset(skb);
        skb->mac.raw = skb->nh.raw;
        skb->nh.raw = skb->data;
        memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
        IPCB(skb)->flags = 0;
        skb->protocol = htons(ETH_P_IPV6);
        skb->pkt_type = PACKET_HOST;
        tunnel->stat.rx_packets++;
        tunnel->stat.rx_bytes += skb->len;
        skb->dev = tunnel->dev;
        dst_release(skb->dst);
        skb->dst = NULL;
        nf_reset(skb);
        ipip6_ecn_decapsulate(iph, skb);
        netif_rx(skb);
        read_unlock(&ipip6_lock);
        return 0;
    }

    icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
    kfree_skb(skb);
    read_unlock(&ipip6_lock);
out:
    return 0;
}

ipip6_tunnel_lookup()函数中,对建立隧道时使用的地址,和传递的地址进行比较,并且对接口进行判断是否打开,只有这两个全部成立时,查找隧道接口才算成功。


static struct ip_tunnel * ipip6_tunnel_lookup(u32 remote, u32 local)
{
    unsigned h0 = HASH(remote);
    unsigned h1 = HASH(local);
    struct ip_tunnel *t;

    for (t = tunnels_r_l[h0^h1]; t; t = t->next) {
        if (local == t->parms.iph.saddr &&
         remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
            return t;
    }
    for (t = tunnels_r[h0]; t; t = t->next) {
        if (remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
            return t;
    }
    for (t = tunnels_l[h1]; t; t = t->next) {
        if (local == t->parms.iph.saddr && (t->dev->flags&IFF_UP))
            return t;
    }
    if ((t = tunnels_wc[0]) != NULL && (t->dev->flags&IFF_UP))
        return t;
    return NULL;
}


您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP