内核基于流的GRE隧道与lwtunnel隧道以及collect_md简析

GRE实现基于内核的IP tunnel隧道框架,ip隧道的框架又是在轻量级lwtunnel隧道基础上实现。本文不阐述轻量级隧道的实现详情,可参看以下链接:https://blog.csdn.net/sinat_20184565/article/details/84952713。


GRE点到点隧道的配置如下:

$ sudo ip tunnel add gre01 mode gre remote 192.168.1.123 local 192.168.1.113 ttl 255
或者
$ sudo ip link add gre01 type gre remote 192.168.1.123 local 192.168.1.113 ttl 255

GRE基于流的隧道配置如下,即由关键字external定义GRE设备,通过配置内核路由系统指定GRE隧道参数:

测试机一,操作系统Ubuntu 17.04,内核版本Linux localhost 4.10.0-42-generic, IP地址:192.168.1.113,GRE配置如下。

$ sudo ip link add gre01 type gre external
$ sudo ip link set gre01 up
$ sudo ip addr add 10.1.1.1/24 dev gre01
$ sudo ip route add 10.1.2.0/24 encap ip dst 192.168.1.129 ttl 10 dev gre01

测试机二,操作系统Ubuntu 18.04.1 LTS,内核版本Linux localhost 4.15.0-42-generic,IP地址:192.168.1.129,GRE配置如下:

$ sudo ip link add gre01 type gre external
$ sudo ip link set gre01 up
$ sudo ip addr add 10.1.2.1/24 dev gre01
$ sudo ip route add 10.1.1.0/24 encap ip dst 192.168.1.113 ttl 10 dev gre01

如果有第三台测试机,假设IP为192.168.1.130,内网为10.1.3.0/24,三台机器之间实现GRE互通,需要在测试机一和二上添加到10.1.3.0/24网段的路由,不需要再创建新的GRE设备,配置如下。

$ sudo ip route add 10.1.3.0/24 encap ip dst 192.168.1.130 ttl 10 dev gre01

相对于点对点配置的GRE,省去GRE设备创建。


GRE流设备创建

以上看到,IP命令使用external关键字创建GRE设备gre01,无需指定其他参数,external与其它参数互斥。内核中的函数ipgre_newlink负责处理创建gre设备的IP命令。其调用ipgre_netlink_parms函数处理netlink命令参数,如下,对于external参数(IP命令通过宏IFLA_GRE_COLLECT_METADATA下发到内核),内核将与新创建的gre设备关联的ip隧道结构成员collect_md设为真true。

static int ipgre_netlink_parms(struct net_device *dev, struct nlattr *data[],
                struct nlattr *tb[], struct ip_tunnel_parm *parms,  __u32 *fwmark)
{
    struct ip_tunnel *t = netdev_priv(dev);

    if (data[IFLA_GRE_COLLECT_METADATA])
        t->collect_md = true;
}

GRE隧道基于ip隧道之上的设备,GRE创建函数ipgre_newlink通过调用ip_tunnel_newlink将新创建的GRE设备所对应的IP隧道注册到内核IP隧道系统中。这样GRE协议接收函数__ipgre_rcv在查找接收隧道(ip_tunnel_lookup)时,才能找到此GRE隧道。

int ip_tunnel_newlink(struct net_device *dev, struct nlattr *tb[], struct ip_tunnel_parm *p, __u32 fwmark)
{
    err = register_netdevice(dev);
    ip_tunnel_add(itn, nt);
}

轻量级隧道

内核IP隧道系统(ip_tunnel_core.c)通过轻量级隧道系统(lwtunnel.c)与路由系统连通。IP隧道系统初始化时注册了lwtunnel_encap_ops结构的轻量级隧道封装操作集ip_tun_lwt_ops用于IPv4,ip6_tun_lwt_ops用于IPv6。

void __init ip_tunnel_core_init(void)
{  
    lwtunnel_encap_add_ops(&ip_tun_lwt_ops, LWTUNNEL_ENCAP_IP);
    lwtunnel_encap_add_ops(&ip6_tun_lwt_ops, LWTUNNEL_ENCAP_IP6);
} 

操作集ip_tun_lwt_ops,其中与IP命令添加带有封装(encap)类型路由相关的回调函数为ip_tun_build_state,用于创建轻量级隧道的主要数据结构lwtunnel_state。

static const struct lwtunnel_encap_ops ip_tun_lwt_ops = {
    .build_state = ip_tun_build_state,
};

如下所示,ip_tun_build_state函数解析IP命令行配置的封装参数,如ID号、目的地址dst和ttl等。解析之后的参数值保存在lwtunnel_state结构的成员tun_info中(ip_tunnel_info类型)。

static int ip_tun_build_state(..., struct lwtunnel_state **ts, ...)
{
    struct ip_tunnel_info *tun_info;
    struct lwtunnel_state *new_state;

    new_state = lwtunnel_state_alloc(sizeof(*tun_info));

    new_state->type = LWTUNNEL_ENCAP_IP;
    tun_info = lwt_tun_info(new_state);

    if (tb[LWTUNNEL_IP_ID])
        tun_info->key.tun_id = nla_get_be64(tb[LWTUNNEL_IP_ID]);

    if (tb[LWTUNNEL_IP_DST])
        tun_info->key.u.ipv4.dst = nla_get_in_addr(tb[LWTUNNEL_IP_DST]);

    if (tb[LWTUNNEL_IP_TTL])
        tun_info->key.ttl = nla_get_u8(tb[LWTUNNEL_IP_TTL]);
}

最终,封装参数及轻量级隧道状态结构保存在路由的下一跳结构fib_nh的成员nh_lwtstate中。

struct fib_nh {
    struct lwtunnel_state   *nh_lwtstate;
};   

GRE流隧道发送

发送的数据包通过路由系统,到达GRE流设备之后,调用其发送函数ipgre_xmit,由于设置了external选项collect_md为真,gre_fb_xmit处理数据包发送,之后,不管其返回值一律返回NETDEV_TX_OK退出。

static netdev_tx_t ipgre_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);

    if (tunnel->collect_md) {
        gre_fb_xmit(skb, dev, skb->protocol);
        return NETDEV_TX_OK;
    }
}

函数gre_fb_xmit完成数据包的GRE封装和发送。要对原始数据包进行封装,需要首先得到保存在轻量级隧道结构(lwtunnel_state)中的封装参数信息,这里通过skb_tunnel_info函数获取。

static inline struct ip_tunnel_info *skb_tunnel_info(const struct sk_buff *skb)
{    
    struct dst_entry *dst = skb_dst(skb);
    if (dst && dst->lwtstate)
        return lwt_tun_info(dst->lwtstate);
}

如果IP命令没有指定源地址,prepare_fb_xmit将查询路由(通过目的IP地址)系统选择源IP地址,gre_build_header封装gre头部信息,iptunnel_xmit封装外层的IP头信息,之后发送数据包。

static void gre_fb_xmit(struct sk_buff *skb, struct net_device *dev, __be16 proto)
{
    struct ip_tunnel_info *tun_info;
    const struct ip_tunnel_key *key;

    tun_info = skb_tunnel_info(skb);
    key = &tun_info->key;

    rt = prepare_fb_xmit(skb, dev, &fl, tunnel_hlen);
    gre_build_header(skb, tunnel_hlen, flags, proto, tunnel_id_to_key32(tun_info->key.tun_id), 0);
    iptunnel_xmit(skb->sk, rt, skb, fl.saddr, key->u.ipv4.dst, IPPROTO_GRE, key->tos, key->ttl, df, false);
}

GRE流隧道接收

接收到GRE数据包之后,__ipgre_rcv函数首先在IP隧道链表中查找匹配的隧道,找到之后,如创建的GRE流设备隧道,其collect_md为真。接下来创建一个接收端的metadata_dst结构,由函数ip_tun_rx_dst完成,之后将此结构通过函数skb_set_dst设置到skb结构中,在ip_tunnel_rcv函数中实现,ip_tunnel_rcv最终接收数据包到内核协议栈中。

static int __ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi, struct ip_tunnel_net *itn, ...)
{   
    struct metadata_dst *tun_dst = NULL;
    struct ip_tunnel *tunnel;
    
    tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, tpi->flags, iph->saddr, iph->daddr, tpi->key);
    if (tunnel) {
        if (__iptunnel_pull_header(skb, hdr_len, tpi->proto, raw_proto, false) < 0)
            goto drop;
        if (tunnel->collect_md)
            tun_dst = ip_tun_rx_dst(skb, flags, tun_id, 0);
        ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
    }
}

在IP层函数ip_rcv_finish中,skb_valid_dst函数如下,在接触路由缓存有效性时,由于之前分配的是metadata_dst结构,DST_METADATA标志处于置位状态,此路由缓存为无效缓存。需要重新查询路由表生成新的路由缓存。

static inline bool skb_valid_dst(const struct sk_buff *skb)
{
    struct dst_entry *dst = skb_dst(skb);
    return dst && !(dst->flags & DST_METADATA);
}

入口路由查询函数ip_route_input_slow,使用skb_tunnel_info函数取得隧道相关信息,比如隧道ID,此信息可用于策略路由的匹配参数。

static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr, ...)
{
    struct ip_tunnel_info *tun_info;
    struct flowi4   fl4;

    tun_info = skb_tunnel_info(skb);
    if (tun_info && !(tun_info->mode & IP_TUNNEL_INFO_TX))
        fl4.flowi4_tun_key.tun_id = tun_info->key.tun_id;
    else
        fl4.flowi4_tun_key.tun_id = 0;
}

skb_tunnel_info函数与之前在GRE隧道发送中提到的不同,之前是由轻量级隧道结构取得隧道信息,此处可从metadata_dst中取出IP隧道信息。

static inline struct ip_tunnel_info *skb_tunnel_info(const struct sk_buff *skb)
{     
    struct metadata_dst *md_dst = skb_metadata_dst(skb);
    struct dst_entry *dst;

    if (md_dst && md_dst->type == METADATA_IP_TUNNEL)
        return &md_dst->u.tun_info;
		
    dst = skb_dst(skb);
    if (dst && dst->lwtstate)
        return lwt_tun_info(dst->lwtstate);
}

内核版本

Linux-4.15

 

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