基于内核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
版权声明:本文为博主原创文章,转载请附上博文链接!
本文链接:https://www.kinber.cn/post/647.html 转载需授权!
推荐本站淘宝优惠价购买喜欢的宝贝: