内核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

 

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页