GRE隧道封装协议及内核处理解析

Generic Routing Encapsulation (GRE)通用路由封装协议,基于IP网络层协议封装以太网报文,可用于在IPSec VPN网络间传输多播路由信息报文,或者在PPTP协议中,承载PPP数据报文。其在数据帧中的位置如下:


 
 
  1. |-------------------|----------------|----------------------|------------------|
  2. | Outer IP Header | GRE Header | Inner IP Header | Payload |
  3. |-------------------|----------------|----------------------|------------------|

GRE头部最小4个字节长度,其后为可选字段,第一个字节中的标志位决定是否存在后面的字段,最大长度为16个字节。以下为一个标准的GRE报头格式:


 
 
  1. |------------------------------------------------------------------------------|
  2. | bit0-3 | 4-12 | 13-15 | 16 - 31 |
  3. |-------------|----------------|-------|---------------------------------------|
  4. | C | | K | S | Reserved0 | Ver | Protocol Type |
  5. |--------------------------------------|---------------------------------------|
  6. | Checksum(optional) | Reserved1(optional) |
  7. |------------------------------------------------------------------------------|
  8. | Key(optional) |
  9. |------------------------------------------------------------------------------|
  10. | Sequence Number (optional) |
  11. |------------------------------------------------------------------------------|

C, K, and S      : 分别对应Checksum、Key和Sequence Number字段,置1表示存在相应的字段,否则无此字段。
Ver                   : GRE版本号(为0),对于PPTP GRE,此字段为1。
Protocol Type  : 封装的以太网协议类型(例如IPv4,此处为0x0800)
Checksum       : 如果C位为1,此字段包含由GRE头部开始的所有数据的校验和
Key                  : 如果K位为1,此字段包含秘钥信息
Sequence Number: 如果S位为1,此字段包含GRE数据包的序号


Linux配置命令


需要两台设备(Ubuntu操作系统)进行配置和测试。一台设备A的IP地址为192.168.1.113,另外一台设备B的IP地址为192.168.1.123。首先在设备A上配置GRE隧道,如下命令:


$ sudo ip tunnel add gre01 mode gre remote 192.168.1.123 local 192.168.1.113 ttl 255
$ sudo ip link set gre01 up
$ sudo ip addr add 10.1.1.1/24 dev gre01

以上ip tunnel命令创建gre01隧道设备,其本地地址为192.168.1.113,源端地址为192.168.1.123,发送数据包时添加的外层IP报头的源IP和目的IP地址将使用这两个地址,ttl字段使用255。隧道设备gre01自身的IP地址配置为10.1.1.1,掩码255.255.255.0。使用IP命令检查gre01接口的配置:

$ sudo ip addr list dev gre01
7: gre01@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1476 qdisc noqueue state UNKNOWN group default qlen 1000
    link/gre 192.168.1.113 peer 192.168.1.123
    inet 10.1.1.1/24 scope global gre01

设备B的配置命令如下:

$ sudo ip tunnel add gre01 mode gre remote 192.168.1.113 local 192.168.1.123 ttl 255
$ sudo ip link set gre01 up
$ sudo ip addr add 10.1.1.2/24 dev gre01

至此,GRE隧道配置完成,使用ping命令测试连通性,在设备A上ping:

$ ping 10.10.10.2
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_req=1 ttl=64 time=0.619 ms

通过WireShark抓包可见完整的GRE报头信息。

以下配置使能GRE的三个选项字段:Checksum、Key和Sequence Number。其中Linux内核支持三个参数的方向性配置,例如从A到B的秘钥使用0x222222,从B到A的秘钥使用0x111111,如果两个方向秘钥相同可使用key参数指定一次即可。校验和csum(icsum和ocsum)和序列号seq(iseq和oseq)同样支持类似的方向性配置。

设备A:
$ sudo ip tunnel add gre01 mode gre remote 192.168.1.123 local 192.168.1.113 ttl 255 ikey 0x111111 okey 0x222222 csum seq
$ sudo ip addr add 10.1.1.1/24 dev gre01
$ sudo ip link set gre01 up 

设备B:
$ sudo ip tunnel add gre01 mode gre remote 192.168.1.113 local 192.168.1.123 ttl 255 ikey 0x222222 okey 0x111111 csum seq
$ sudo ip addr add 10.1.1.2/24 dev gre01
$ sudo ip link set gre01 up 

再次WireShark抓包,查看GRE完整的头部信息:


最后,使用如下命令删除GRE隧道设备:

$ sudo ip link set gre0 down
$ sudo ip tunnel del gre0

GRE内核实现

内核注册ipgre_link_ops处理ip命令创建gre隧道的请求,GRE隧道不分类型(gre/gretap/erspan)在内核中统一由ip_tunnel结构体表示。ipgre_newlink函数处理隧道的建立过程。


 
 
  1. static struct rtnl_link_ops ipgre_link_ops __read_mostly = {
  2. .kind = "gre",
  3. .priv_size = sizeof(struct ip_tunnel),
  4. .newlink = ipgre_newlink,
  5. };

函数ipgre_newlink在进行最初的参数解析工作之后,调用ip_tunnel_newlink函数处理隧道设备的建立,首先是向系统注册隧道网络设备,其次如果用户没有指定此设备的MAC二层地址,随机生成一个地址。


 
 
  1. err = register_netdevice(dev);
  2. if (dev->type == ARPHRD_ETHER && !tb[IFLA_ADDRESS])
  3. eth_hw_addr_random(dev);
  4. mtu = ip_tunnel_bind_dev(dev);
  5. if (!tb[IFLA_MTU])
  6. dev->mtu = mtu;
  7. ip_tunnel_add(itn, nt);

ip_tunnel_bind_dev函数根据隧道的目的IP地址等信息查询出口路由信息,获得物理出口设备。根据出口设备的硬件头部长度和所需头部空间,与隧道头部长度之和设置隧道设备的头部所需空间。隧道设备的MTU值需要减去隧道头部长度与外层IP头部长度之和。对于隧道设备而言,hard_header_len为0。


 
 
  1. int t_hlen = tunnel->hlen + sizeof(struct iphdr);
  2. if (tdev) {
  3. hlen = tdev->hard_header_len + tdev->needed_headroom;
  4. mtu = tdev->mtu;
  5. }
  6. dev->needed_headroom = t_hlen + hlen;
  7. mtu -= (dev->hard_header_len + t_hlen);

GRE隧道发送

GRE隧道设备的操作函数集为ipgre_netdev_ops,其中发送函数ipgre_xmit负责隧道数据帧的发送工作:


 
 
  1. static const struct net_device_ops ipgre_netdev_ops = {
  2. .ndo_init = ipgre_tunnel_init,
  3. .ndo_start_xmit = ipgre_xmit,
  4. };
  5. static void ipgre_tunnel_setup(struct net_device *dev)
  6. {
  7. dev->netdev_ops = &ipgre_netdev_ops;
  8. }

隧道数据帧的发送首先是一个组包的过程,内核协议栈在发送数据帧时,根据路由表查找到出口设备,此时为gre隧道设备,为数据包添加二层头部信息,见函数ipgre_header回调。根据设备的隧道信息初始化gre头部的标志和协议字段,以及初始化外层IP头部。


 
 
  1. static const struct header_ops ipgre_header_ops = {
  2. .create = ipgre_header,
  3. .parse = ipgre_header_parse,
  4. };
  5. static int ipgre_header(struct sk_buff *skb, ...)
  6. {
  7. greh = (struct gre_base_hdr *)(iph+ 1);
  8. greh->flags = gre_tnl_flags_to_gre_flags(t->parms.o_flags);
  9. greh->protocol = htons(type);
  10. memcpy(iph, &t->parms.iph, sizeof(struct iphdr));
  11. if (saddr)
  12. memcpy(&iph->saddr, saddr, 4);
  13. if (daddr)
  14. memcpy(&iph->daddr, daddr, 4);
  15. }

之后ipgre_xmit函数调用__gre_xmit函数,从隧道设备中取出隧道结构体,判断如果设置了发送方向的序列号功能,增加隧道的发送序列号。随后由函数gre_build_header填充GRE头部信息。由于在ipgre_header函数中已经将外层IP头部信息填写完成,此处主要根据GRE头部的标志字段增加可选字段信息。 最后由函数ip_tunnel_xmit发送完成的数据包。


 
 
  1. struct ip_tunnel *tunnel = netdev_priv(dev);
  2. if (tunnel->parms.o_flags & TUNNEL_SEQ)
  3. tunnel->o_seqno++;
  4. gre_build_header(skb, tunnel->tun_hlen, tunnel->parms.o_flags, proto, tunnel->parms.o_key, htonl(tunnel->o_seqno));
  5. ip_tunnel_xmit(skb, dev, tnl_params, tnl_params->protocol);

GRE隧道接收

GRE通过net_gre_protocol网络协议结构体注册到协议栈处理流程中。所有协议号为IPPROTO_GRE(47)的数据帧都交由net_gre_protocol中的handler即入口函数gre_rcv处理。


 
 
  1. static const struct net_protocol net_gre_protocol = {
  2. .handler = gre_rcv,
  3. .err_handler = gre_err,
  4. };
  5. static int __ init gre_init(void)
  6. {
  7. inet_add_protocol(&net_gre_protocol, IPPROTO_GRE);
  8. }

GRE数据包的接收入口函数gre_rcv,位于net/ipv4/gre_demux.c文件中,其根据GRE头部的版本字段,调用不同的处理函数。目前内核支持的GRE协议版本有两个GREPROTO_CISCO(0)和GREPROTO_PPTP(1),不同版本的GRE处理函数通过gre_add_protocol注册到静态GRE协议数组gre_proto中,gre_rcv函数根据版本号调用不同的回调函数进行处理。除GRE版本0的协议处理结构net_gre_protocol外,GRE版本1的处理结构为gre_pptp_protocol。


 
 
  1. static int gre_rcv(struct sk_buff *skb)
  2. {
  3. const struct gre_protocol *proto;
  4. ver = skb->data[ 1]& 0x7f;
  5. proto = rcu_dereference(gre_proto[ver]);
  6. ret = proto->handler(skb);
  7. }
  8. static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly;

本文仅涉及GRE版本0的相关处理过程。代码位于net/ipv4/ip_gre.c文件中,内核协议处理结构为gre_protocol,其注册到了上文中的gre_proto数组中。在接收到版本为0的数据包时,调用本文件中的gre_rcv(与文件gre_demux.c中的gre_rcv不同)进行处理。


 
 
  1. static const struct gre_protocol ipgre_protocol = {
  2. .handler = gre_rcv,
  3. .err_handler = gre_err,
  4. };

函数gre_rcv首先调用gre_parse_header函数解析GRE头部信息,其中最重要的是根据gre头部的第一个字节的标志位确定头部长度,如果存在校验和字段,则进行校验和的验证。需要注意的是如果封装的是WCCP协议,将隧道信息中的协议字段修正为htons(ETH_P_IP)IP协议,并且如果是WCCPv2,GRE头部长度额外增加4个字节。
 


 
 
  1. if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) {
  2. tpi->proto = proto;
  3. if ((*(u8 *)options & 0xF0) != 0x40)
  4. hdr_len += 4;
  5. }

头部信息解析完成之后,gre_rcv判断如果内层的协议为交换接口远程分析封装协议(Encapsulated Remote Switched Port Analyzer),调用erspan_rcv进行处理。否则调用ipgre_rcv进行处理,正是此函数处理我们之前使用ip命令配置的gre隧道传输。


 
 
  1. if (unlikely(tpi.proto == htons(ETH_P_ERSPAN))) {
  2. if (erspan_rcv(skb, &tpi, hdr_len) == PACKET_RCVD)
  3. return 0;
  4. }
  5. if (ipgre_rcv(skb, &tpi, hdr_len) == PACKET_RCVD)
  6. return 0;

核心处理函数__ipgre_rcv,其根据数据包信息完成tunnel隧道的查找,调用ip_tunnel_rcv函数接收数据包。


 
 
  1. static int __ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi, struct ip_tunnel_net *itn, int hdr_len, bool raw_proto)
  2. {
  3. tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, tpi->flags, iph->saddr, iph->daddr, tpi->key);
  4. if (tunnel)
  5. ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
  6. }

 
内核版本

Linux-4.15

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值