07
2019
05
17:33:28

内核IP隧道的FallBack设备与隧道

基于内核IP TUNNEL体系的隧道,在初始化时默认创建一个FallBack设备及相应的FallBack隧道。例如GRE类隧道、IPIP和VTI隧道。

GRE IPv4模块加载之后,默认创建三个设备,分别为gre0、gretap0和erspan0,IPIP隧道默认创建tunl0名字的设备,VTI隧道创建的默认设备名为ip_vti0。这些设备为隧道的FallBack设备。每种类型的FB设备每个网络命名空间中仅有一个,并且不能在命名空间之间移动。FallBack设备及FallBack隧道不同于其它的IP隧道及设备,其没有进行隧道封装所需的源地址、目的地址、秘钥(GRE)、序列号等信息。


使用ip命令查看系统中的IPIP、GRE类与VTI隧道的默认FB设备:


$ ip link show

2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000

    link/ipip 0.0.0.0 brd 0.0.0.0

3: gre0@NONE: <NOARP> mtu 1476 qdisc noop state DOWN mode DEFAULT group default qlen 1000

    link/gre 0.0.0.0 brd 0.0.0.0

4: gretap0@NONE: <BROADCAST,MULTICAST> mtu 1462 qdisc noop state DOWN mode DEFAULT group default qlen 1000

    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

5: erspan0@NONE: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN mode DEFAULT group default qlen 1000

    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

6: ip_vti0@NONE: <NOARP> mtu 1332 qdisc noop state DOWN mode DEFAULT group default qlen 1000

    link/ipip 0.0.0.0 brd 0.0.0.0

以下配置一个通用的GRE隧道设备gre1,以及显示其与GRE隧道默认FB设备的区别:


$ sudo ip tunnel add gre1 mode gre remote 192.168.1.123 local 192.168.1.113 ttl 255 ikey 0x111111 okey 0x222222 csum seq

$ ip -d link show type gre

5: gre0@NONE: <NOARP> mtu 1476 qdisc noop state DOWN mode DEFAULT group default qlen 1000

    link/gre 0.0.0.0 brd 0.0.0.0 promiscuity 0 

    gre remote any local any ttl inherit nopmtudisc addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535  

12: gre1@NONE: <POINTOPOINT,NOARP> mtu 1464 qdisc noop state DOWN mode DEFAULT group default qlen 1000

    link/gre 192.168.1.113 peer 192.168.1.123 promiscuity 0 

    gre remote 192.168.1.123 local 192.168.1.113 ttl 255 ikey 0.17.17.17 okey 0.34.34.34 iseq oseq icsum ocsum addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 

FB设备的创建

参见初始化函数ip_tunnel_init_net,其通过__ip_tunnel_create调用创建FB设备,此设备的访问地址保存在当前网络命名空间的ip_tunnel_net结构成员fb_tunnel_dev指针中,每个命名空间中仅有一个对应于指定ID(ip_tnl_net_id)的FB设备。GRE设备、GRETAP设备和ERSPAN设备具有不同的ID。devname为指定的FB设备名称。为设备结构的features成员添加仅限本地网络命名空间(NETIF_F_NETNS_LOCAL表明不可移动)。


int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id, struct rtnl_link_ops *ops, char *devname)

{

    struct ip_tunnel_net *itn = net_generic(net, ip_tnl_net_id);

    struct ip_tunnel_parm parms;

 

    if (devname)

        strlcpy(parms.name, devname, IFNAMSIZ);

    itn->fb_tunnel_dev = __ip_tunnel_create(net, ops, &parms);

 

    if (!IS_ERR(itn->fb_tunnel_dev)) {

        itn->fb_tunnel_dev->features |= NETIF_F_NETNS_LOCAL;

        itn->fb_tunnel_dev->mtu = ip_tunnel_bind_dev(itn->fb_tunnel_dev);

        ip_tunnel_add(itn, netdev_priv(itn->fb_tunnel_dev));

    }

}

函数ip_tunnel_add将FB设备对应的ip_tunnel隧道结构按照哈希值挂载到网络命名空间的全局隧道链表中。其中FB设备对应的IP隧道结构在以上函数__ip_tunnel_create中分配网络设备时作为私有结构一并分配出来,哈希值的计算是按照隧道参数ip_tunnel_parm结构的成员:输入秘钥(i_key)和隧道远端IP(remote)的值计算而来。最终得到的hash值作为索引,对应到ip_tunnel_net类型itn的数据成员tunnels[hash]而得到链表的头部指针,即ip_bucket的返回结果。将FB设备对应的隧道结构链接到此链表中。由于FB设备的隧道参数parms仅初始化了name成员的值,i_key与remote都为空,计算的hash值为零,所以FB设备的隧道结构链接在itn->tunnel[0]所指向的链表上。


static struct hlist_head *ip_bucket(struct ip_tunnel_net *itn, struct ip_tunnel_parm *parms)

{

    __be32 i_key = parms->i_key;

 

    if (parms->iph.daddr && !ipv4_is_multicast(parms->iph.daddr))

        remote = parms->iph.daddr;

    else

        remote = 0;

    if (!(parms->i_flags & TUNNEL_KEY) && (parms->i_flags & VTI_ISVTI))

        i_key = 0;

 

    h = ip_tunnel_hash(i_key, remote);

    return &itn->tunnels[h];

}

static void ip_tunnel_add(struct ip_tunnel_net *itn, struct ip_tunnel *t)

{

    struct hlist_head *head = ip_bucket(itn, &t->parms);

 

    if (t->collect_md)

        rcu_assign_pointer(itn->collect_md_tun, t);

    hlist_add_head_rcu(&t->hash_node, head);

}

FB设备在__ip_tunnel_create函数中被分配和注册。如果名字参数为空,使用kind值与%d索引组成的字符串作为设备名字,GRE隧道就是如此,未指定name,其kind值为gre,由于FB设备是第一个注册的gre设备,系统为其分配了设备名称gre0。


static struct net_device *__ip_tunnel_create(struct net *net, const struct rtnl_link_ops *ops, struct ip_tunnel_parm *parms)

{

    struct ip_tunnel *tunnel;

 

    if (parms->name[0])

        strlcpy(name, parms->name, IFNAMSIZ);

    else {

        strlcpy(name, ops->kind, IFNAMSIZ);

        strncat(name, "%d", 2);

    }

    dev = alloc_netdev(ops->priv_size, name, NET_NAME_UNKNOWN, ops->setup);

    err = register_netdevice(dev);

}

以下GRE三种类型的隧道初始化程序,其中不同的三个ID值ipgre_net_id、gre_tap_net_id和erspan_net_id分别在网络命名空间中对应着三个不同的隧道结构,每个命名空间隧道结构(ip_tunnel_net)对应一个FB设备。如前所述GRE隧道未指定FB设备名(最后一个参数为NULL),其设备名由ipgre_link_ops的kind成员指定。GRETAP隧道和ERSPAN隧道明确指定了FB设备名。如果后两个隧道不明确指定设备名,ip_tunnel_init_net函数也可以由ipgre_tap_ops和erspan_link_ops结构的kind成员值(分别为gretap和erspan)和索引正确的推导出。


    ipgre_init_net(struct net *net)

   |--- ip_tunnel_init_net(net, ipgre_net_id, &ipgre_link_ops, NULL);

 

    ipgre_tap_init_net(struct net *net)

       |--- ip_tunnel_init_net(net, gre_tap_net_id, &ipgre_tap_ops, "gretap0");

 

    erspan_init_net(struct net *net)

       |--- ip_tunnel_init_net(net, erspan_net_id, &erspan_link_ops, "erspan0");

以下为IPIP隧道和VTI隧道的初始化,可见二者指定的FB隧道设备名称为tunnl0和ip_vti0:


    ipip_init_net(struct net *net)

       |--- ip_tunnel_init_net(net, ipip_net_id, &ipip_link_ops, "tunl0");

 

    vti_init_net(struct net *net)

       |--- ip_tunnel_init_net(net, vti_net_id, &vti_link_ops, "ip_vti0");

FB隧道设备接收

对于接收到的隧道封装数据包,在处理之前,内核需要知道其属于哪一个隧道。ip_tunnel_lookup函数查找合适的隧道,如果内核中并没有相对应的隧道,查找函数将使用FB设备对应的隧道作为返回值。因为FB隧道没有源和目的地址等信息,理论上和所有数据包都匹配。


struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,

                   int link, __be16 flags, __be32 remote, __be32 local, __be32 key)

{

    if (itn->fb_tunnel_dev && itn->fb_tunnel_dev->flags & IFF_UP)

        return netdev_priv(itn->fb_tunnel_dev);

}

在找到隧道之后,__iptunnel_pull_header函数剥掉隧道的头部数据,

隧道内层的数据包交由ip_tunnel_rcv函数处理。由于此处的隧道为FB隧道,其不具有校验的功能,所以如果数据包设置了校验位,FB隧道将丢弃此数据包。


static int __ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi, ...)

{  

    struct ip_tunnel *tunnel;

 

    tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, tpi->flags, iph->saddr, iph->daddr, tpi->key);

    if (tunnel) {

        __iptunnel_pull_header(skb, hdr_len, tpi->proto, raw_proto, false);

        ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);

        return PACKET_RCVD;

    }

}

函数skb_reset_network_header将网络头指针重新指向内层数据的IP头部,skb_scrub_packet消除skb中的一些数据包记录信息,将skb结构的成员pkt_type设置为PACKET_HOST,随后由函数gro_cells_receive将数据包送往内核协议栈。此时作为一个普通的IP数据包,协议栈根据IP头部信息查询路由表,决定数据包时本地接收或者进行转发。


int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb, const struct tnl_ptk_info *tpi, ...)

{

    if ((!(tpi->flags&TUNNEL_CSUM) &&  (tunnel->parms.i_flags&TUNNEL_CSUM)) ||

         ((tpi->flags&TUNNEL_CSUM) && !(tunnel->parms.i_flags&TUNNEL_CSUM))) {

        tunnel->dev->stats.rx_crc_errors++;

        tunnel->dev->stats.rx_errors++;

        goto drop;

    }

    skb_reset_network_header(skb);

    skb_scrub_packet(skb, !net_eq(tunnel->net, dev_net(tunnel->dev)));

    gro_cells_receive(&tunnel->gro_cells, skb);

}

FB隧道设备必须配置有与内存数据包IP地址相同网段的地址,否者数据包将被协议栈路由系统的(rp_filter)反向路径检查所丢弃。


内核版本

Linux-4.15

--------------------- 

作者:redwingz 

来源:CSDN 

原文:https://blog.csdn.net/sinat_20184565/article/details/85041232 

版权声明:本文为博主原创文章,转载请附上博文链接!




推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

本文链接:https://hqyman.cn/post/647.html 非本站原创文章欢迎转载,原创文章需保留本站地址!

分享到:
打赏





休息一下~~


« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

请先 登录 再评论,若不是会员请先 注册

您的IP地址是: