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