IP Tunnel one-to-many用法

Linux IP Tunnel有IPIP、Gre、Sit,使用虚拟网络中常用的overlay技术,一般需要直接配置local 和 remote address,但是在一些SDN的虚拟网络中常常会存在大量的对端,这就需要配置很多Tunnel口。管理起来比较麻烦。

解决这个问题的一个办法,就是在配置IP Tunnel时,不指定remote address,而是在指向Tunnel口的路由中通过Nexthop指定remote address。这种tunnel在linux 内核代码的注解中叫NBMA Tunnel。

而更彻底一点,使用FallBack隧道,它没有进行隧道封装所需的源地址、目的地址、秘钥(GRE)、序列号等信息,即tunnel para为空的Tunnel设备。

这种设备在系统初始化的时候,如果内核加载了对应的tunnel模块后 实际上已经创建好了,名称固定,GRE IPv4模块加载之后,默认创建三个设备,分别为gre0、gretap0和erspan0,IPIP隧道默认创建tunl0名字的设备,VTI隧道创建的默认设备名为ip_vti0。

## 默认看不到这些接口

#ip link

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000

    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000

    link/ether 52:54:00:cf:65:d1 brd ff:ff:ff:ff:ff:ff

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000

    link/ether 52:54:00:b6:4d:0d brd ff:ff:ff:ff:ff:ff

## 可是如果我们创建一个没有参数的tunnel口,又会提示已经存在

#ip link add ipipx type ipip 

RTNETLINK answers: File exists

## 我们再看的化,又能够看到了

#ip link

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000

    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000

    link/ether 52:54:00:cf:65:d1 brd ff:ff:ff:ff:ff:ff

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000

    link/ether 52:54:00:b6:4d:0d brd ff:ff:ff:ff:ff:ff

18: 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
    

用法很简单,如果接口配置如下:
host1:
lo: 11.1.1.1/32 ; eth1: 192.168.122.11
host2:
lo: 12.1.1.1/32 ; eth1: 192.168.122.12
host3:
lo: 13.1.1.1/32 ; eth1: 192.168.122.13

那么,host1 需要两条到达 host2和host3的tunnel,我们在只使用默认的tunl0 FallBack隧道的情况下只需要配置两条路由(对端同样配置):

12.1.1.1 via 192.168.122.12 dev tunl0 onlink

13.1.1.1 via 192.168.122.13 dev tunl0 onlink

注意这里需要指定onlink,因为下一跳和tunl0不是一个网络(网段),直接配置会出错。

执行 ping 12.1.1.1 -I 11.1.1.1 时,会查找到第一条路由,将报文送入路由的dev,即tunl0口,tunl0 口会将nexthop作为tunnel的外层dst ip

相关原理我们可以简单看一下代码:

IP tunnel的发送函数都会调用:
IP Tunnel one-to-many用法

void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,

                    const struct iphdr *tnl_params, u8 protocol)

{

    struct ip_tunnel *tunnel = netdev_priv(dev);

    const struct iphdr *inner_iph;

    struct flowi4 fl4;

    u8    tos, ttl;

    __be16 df;        

    struct rtable *rt; /* Route to the other host */

    unsigned int max_headroom; /* The extra header space needed */

    __be32 dst;

    bool connected;

    inner_iph = (const struct iphdr *)skb_inner_network_header(skb);

    connected = (tunnel->parms.iph.daddr != 0);

    memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));

    dst = tnl_params->daddr;

    if (dst == 0) {           // ========= dst 为空

        /* NBMA tunnel */

        if (!skb_dst(skb)) {

            dev->stats.tx_fifo_errors++;

            goto tx_error;

        }

    if (skb->protocol == htons(ETH_P_IP)) {

        rt = skb_rtable(skb);                               // ========= dst 为空的时候,dst取路由的nexthop,这里的路由在报文送到tunnel口之前就已经查好挂在skb中了

        dst = rt_nexthop(rt, inner_iph->daddr);       

    }

#if IS_ENABLED(CONFIG_IPV6)

    else if (skb->protocol == htons(ETH_P_IPV6)) {

。。。。。。

    }

。。。。。。



    init_tunnel_flow(&fl4, protocol, dst, tnl_params->saddr,

    tunnel->parms.o_key, RT_TOS(tos), tunnel->parms.link);

    if (ip_tunnel_encap(skb, tunnel, &protocol, &fl4) < 0)

        goto tx_error;

    //  ===============  这里,以及下面的 ip_route_output_key 函数会对fl4 赋值,其中的tunnel的外层 src ip,是根据nexthop 查找的tunnel 外层dst ip的路由对应的出接口的ip地址。

    rt = connected ? dst_cache_get_ip4(&tunnel->dst_cache, &fl4.saddr) :NULL;

    if (!rt) {

        rt = ip_route_output_key(tunnel->net, &fl4);

。。。。。。

    }

    // ======== 找到了local 和 remote ip, iptunnel_xmit根据这些信息封装外层ip头,发出去

     iptunnel_xmit(NULL, rt, skb, fl4.saddr, fl4.daddr, protocol, tos, ttl, df, !net_eq(tunnel->net, dev_net(dev)));

    return;

。。。。。。

}

其实不仅是ip tunnel,其它类型的tunnel也有类似机制,比如vxlan,它是一种二层tunnel,转发的一句不是ip,而是mac地址,也可以不指定remote address,而是用过二层的fdb表项,指定remote,从而使用一个tunnel口完成到达多个对端的效果。

上一篇:bond实验


下一篇:使用并发 ssh 连接来提升捞日志脚本执行效率