协议简介 GRE(Generic Routing Encapsulation,通用路由封装)协议是对某些网络层协议(如IP和IPX)的数据报文进行封装,使这些被封装的数据报文能够在另一个网络层协议(如IP)中传输。GRE采用了Tunnel(隧道)技术,是VPN(Virtual Private Network)的第三层隧道协议。
Tunnel是一个虚拟的点对点的连接,提供了一条通路使封装的数据报文能够在这个通路上传输,并且在一个Tunnel的两端分别对数据报进行封装及解封装。
报文格式 封装好的报文的形式如下
Payload可以是任何协议,GRE允许非IP协议在有效载荷中被传输。如果 Delivery header 是ipv4的头,则GRE被归入IP协议,协议号为47(Linux 支持的协议号)。
一个封装在IP Tunnel中的X协议报文的格式如下:
需要封装和传输的数据报文,称之为净荷(Payload),净荷的协议类型为乘客协议(Passenger Protocol)。系统收到一个净荷后,首先使用封装协议(Encapsulation Protocol)对这个净荷进行GRE封装,即把乘客协议报文进行了“包装”,加上了一个GRE头部成为GRE报文;然后再把封装好的原始报文和GRE头部封装在IP报文中,这样就可完全由IP层负责此报文的前向转发(Forwarding)。通常把这个负责前向转发的IP协议称为传输协议(Delivery Protocol或者Transport Protocol)。
根据传输协议的不同,可以分为GRE over IPv4和GRE over IPv6两种隧道模式。以IPV4为例分析封装、解封过程,拓扑如下
封装过程 Private A中发送报文,检查报文头中的目的地址域来确定如何发送此包; 若报文的目的地址要经过Tunnel才能到达,则设备将此报文发给相应的Tunnel接口; Tunnel口收到此报文后进行GRE封装,在封装IP报文头后,设备根据此IP包的目的地址及路由表对报文进行转发,从相应的网络接口发送出去。 解封过程 Private B从Tunnel接口收到IP报文,检查目的地址; 发现目的地是本路由器,则Router B剥掉此报文的IP报头,交给GRE协议处理(进行检验密钥、检查校验和及报文的序列号等); GRE协议完成相应的处理后,剥掉GRE报头,再交由内部协议对此数据报进行后续的转发处理。 GRE的安全选项 为了提高GRE隧道的安全性,GRE还支持由用户选择设置Tunnel接口的识别关键字(或称密钥),和对隧道封装的报文进行端到端校验。
在RFC1701 中规定:
若GRE报文头中的Key标识位置1,则收发双方将进行通道识别关键字的验证,只有Tunnel两端设置的识别关键字完全一致时才能通过验证,否则将报文丢弃。 若GRE报文头中的Checksum标识位置1,则校验和有效。发送方将根据GRE头及Payload信息计算校验和,并将包含校验和的报文发送给对端。接收方对接收到的报文计算校验和,并与报文中的校验和比较,如果一致则对报文进一步处理,否则丢弃。 Linux对GRE的支持 代码基于 kernel 3.10.105。
模块初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static int __init gre_init (void ) { pr_info("GRE over IPv4 demultiplexor driver\n" ); if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0 ) { pr_err("can't add protocol\n" ); return -EAGAIN; } if (inet_add_offload(&gre_offload, IPPROTO_GRE)) { pr_err("can't add protocol offload\n" ); inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); return -EAGAIN; } return 0 ; }
而 net_gre_protocol
定义为
1 2 3 4 5 static const struct net_protocol net_gre_protocol = { .handler = gre_rcv, .err_handler = gre_err, .netns_ok = 1 , };
因此,在收到GRE报文并上送到本地后,在函数ip_local_deliver_finish
中会调用函数gre_rcv
。
收包 1 2 TIPS: 接口创建函数 ip_tunnel_create。
gre_rcv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static int gre_rcv (struct sk_buff *skb) { const struct gre_protocol *proto ; u8 ver; int ret; if (!pskb_may_pull(skb, 12 )) goto drop; ver = skb->data[1 ]&0x7f ; if (ver >= GREPROTO_MAX) goto drop; rcu_read_lock(); proto = rcu_dereference(gre_proto[ver]); if (!proto || !proto->handler) goto drop_unlock; ret = proto->handler(skb); rcu_read_unlock(); return ret; drop_unlock: rcu_read_unlock(); drop: kfree_skb(skb); return NET_RX_DROP; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static int __init ipgre_init (void ) { int err; err = register_pernet_device(&ipgre_net_ops); if (err < 0 ) return err; err = register_pernet_device(&ipgre_tap_net_ops); if (err < 0 ) goto pnet_tap_faied; err = gre_add_protocol(&ipgre_protocol, GREPROTO_CISCO); if (err < 0 ) { pr_info("%s: can't add protocol\n" , __func__); goto add_proto_failed; } …… } -->> static const struct gre_protocol ipgre_protocol = { .handler = ipgre_rcv, .err_handler = ipgre_err, };
ipgre_rcv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static int ipgre_rcv (struct sk_buff *skb) { struct net *net = dev_net(skb->dev); struct ip_tunnel_net *itn ; const struct iphdr *iph ; struct ip_tunnel *tunnel ; struct tnl_ptk_info tpi ; int hdr_len; bool csum_err = false ; if (parse_gre_header(skb, &tpi, &csum_err, &hdr_len) < 0 ) goto drop; if (tpi.proto == htons(ETH_P_TEB)) itn = net_generic(net, gre_tap_net_id); else itn = net_generic(net, ipgre_net_id); iph = ip_hdr(skb); tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, tpi.flags, iph->saddr, iph->daddr, tpi.key); if (tunnel) { skb_pop_mac_header(skb); ip_tunnel_rcv(tunnel, skb, &tpi, hdr_len, log_ecn_error); return 0 ; } icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0 ); drop: kfree_skb(skb); return 0 ; }
ip_tunnel_rcv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 int ip_tunnel_rcv (struct ip_tunnel *tunnel, struct sk_buff *skb, const struct tnl_ptk_info *tpi, int hdr_len, bool log_ecn_error) { struct pcpu_tstats *tstats ; const struct iphdr *iph = ip_hdr(skb); int err; secpath_reset(skb); skb->protocol = tpi->proto; skb->mac_header = skb->network_header; __pskb_pull(skb, hdr_len); skb_postpull_rcsum(skb, skb_transport_header(skb), tunnel->hlen); #ifdef CONFIG_NET_IPGRE_BROADCAST if (ipv4_is_multicast(iph->daddr)) { if (rt_is_output_route(skb_rtable(skb))) goto drop; tunnel->dev->stats.multicast++; skb->pkt_type = PACKET_BROADCAST; } #endif 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; } if (tunnel->parms.i_flags&TUNNEL_SEQ) { if (!(tpi->flags&TUNNEL_SEQ) || (tunnel->i_seqno && (s32)(ntohl(tpi->seq) - tunnel->i_seqno) < 0 )) { tunnel->dev->stats.rx_fifo_errors++; tunnel->dev->stats.rx_errors++; goto drop; } tunnel->i_seqno = ntohl(tpi->seq) + 1 ; } if (tunnel->dev->type == ARPHRD_ETHER) { if (!pskb_may_pull(skb, ETH_HLEN)) { tunnel->dev->stats.rx_length_errors++; tunnel->dev->stats.rx_errors++; goto drop; } iph = ip_hdr(skb); skb->protocol = eth_type_trans(skb, tunnel->dev); skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN); } skb->pkt_type = PACKET_HOST; __skb_tunnel_rx(skb, tunnel->dev); skb_reset_network_header(skb); err = IP_ECN_decapsulate(iph, skb); ………… gro_cells_receive(&tunnel->gro_cells, skb); return 0 ; drop: kfree_skb(skb); return 0 ; }
gro_cells_receive 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static inline void gro_cells_receive (struct gro_cells *gcells, struct sk_buff *skb) { struct gro_cell *cell = gcells->cells; struct net_device *dev = skb->dev; if (!cell || skb_cloned(skb) || !(dev->features & NETIF_F_GRO)) { netif_rx(skb); return ; } if (skb_rx_queue_recorded(skb)) cell += skb_get_rx_queue(skb) & gcells->gro_cells_mask; if (skb_queue_len(&cell->napi_skbs) > netdev_max_backlog) { atomic_long_inc(&dev->rx_dropped); kfree_skb(skb); return ; } spin_lock(&cell->napi_skbs.lock); __skb_queue_tail(&cell->napi_skbs, skb); if (skb_queue_len(&cell->napi_skbs) == 1 ) napi_schedule(&cell->napi); spin_unlock(&cell->napi_skbs.lock); }
发包 模块初始化时,注册的链路处理函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 static struct rtnl_link_ops ipgre_link_ops __read_mostly = { .kind = "gre" , …… .setup = ipgre_tunnel_setup, …… }; static struct rtnl_link_ops ipgre_tap_ops __read_mostly = { .kind = "gretap" , …… .setup = ipgre_tap_setup, …… };
以’gretap’为例分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static void ipgre_tap_setup (struct net_device *dev) { ether_setup(dev); dev->netdev_ops = &gre_tap_netdev_ops; ip_tunnel_setup(dev, gre_tap_net_id); } -->> static const struct net_device_ops gre_tap_netdev_ops = { .ndo_init = gre_tap_init, .ndo_uninit = ip_tunnel_uninit, .ndo_start_xmit = gre_tap_xmit, .ndo_set_mac_address = eth_mac_addr, .ndo_validate_addr = eth_validate_addr, .ndo_change_mtu = ip_tunnel_change_mtu, .ndo_get_stats64 = ip_tunnel_get_stats64, };
gre_tap_xmit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static netdev_tx_t gre_tap_xmit (struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); skb = handle_offloads(tunnel, skb); if (IS_ERR(skb)) goto out; if (skb_cow_head(skb, dev->needed_headroom)) goto free_skb; __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB)); return NETDEV_TX_OK; …… }
__gre_xmit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static void __gre_xmit(struct sk_buff *skb, struct net_device *dev, const struct iphdr *tnl_params, __be16 proto) { struct ip_tunnel *tunnel = netdev_priv(dev); struct tnl_ptk_info tpi ; if (likely(!skb->encapsulation)) { skb_reset_inner_headers(skb); skb->encapsulation = 1 ; } tpi.flags = tunnel->parms.o_flags; tpi.proto = proto; tpi.key = tunnel->parms.o_key; if (tunnel->parms.o_flags & TUNNEL_SEQ) tunnel->o_seqno++; tpi.seq = htonl(tunnel->o_seqno); skb = gre_build_header(skb, &tpi, tunnel->hlen); if (unlikely(!skb)) { dev->stats.tx_dropped++; return ; } ip_tunnel_xmit(skb, dev, tnl_params); }
ip_tunnel_xmit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 void ip_tunnel_xmit (struct sk_buff *skb, struct net_device *dev, const struct iphdr *tnl_params) { struct ip_tunnel *tunnel = netdev_priv(dev); const struct iphdr *inner_iph ; struct iphdr *iph ; struct flowi4 fl4 ; u8 tos, ttl; __be16 df; struct rtable *rt ; struct net_device *tdev ; unsigned int max_headroom; __be32 dst; inner_iph = (const struct iphdr *)skb_inner_network_header(skb); memset (IPCB(skb), 0 , sizeof (*IPCB(skb))); dst = tnl_params->daddr; if (dst == 0 ) { if (skb_dst(skb) == NULL ) { dev->stats.tx_fifo_errors++; goto tx_error; } if (skb->protocol == htons(ETH_P_IP)) { rt = skb_rtable(skb); dst = rt_nexthop(rt, inner_iph->daddr); } …… else goto tx_error; } tos = tnl_params->tos; if (tos & 0x1 ) { tos &= ~0x1 ; if (skb->protocol == htons(ETH_P_IP)) tos = inner_iph->tos; else if (skb->protocol == htons(ETH_P_IPV6)) tos = ipv6_get_dsfield((const struct ipv6hdr *)inner_iph); } rt = ip_route_output_tunnel(dev_net(dev), &fl4, tunnel->parms.iph.protocol, dst, tnl_params->saddr, tunnel->parms.o_key, RT_TOS(tos), tunnel->parms.link); if (IS_ERR(rt)) { dev->stats.tx_carrier_errors++; goto tx_error; } tdev = rt->dst.dev; if (tdev == dev) { ip_rt_put(rt); dev->stats.collisions++; goto tx_error; } if (tnl_update_pmtu(dev, skb, rt, tnl_params->frag_off)) { ip_rt_put(rt); goto tx_error; } …… ttl = tnl_params->ttl; if (ttl == 0 ) { if (skb->protocol == htons(ETH_P_IP)) ttl = inner_iph->ttl; …… else ttl = ip4_dst_hoplimit(&rt->dst); } df = tnl_params->frag_off; if (skb->protocol == htons(ETH_P_IP)) df |= (inner_iph->frag_off&htons(IP_DF)); max_headroom = LL_RESERVED_SPACE(tdev) + sizeof (struct iphdr) + rt->dst.header_len; if (max_headroom > dev->needed_headroom) dev->needed_headroom = max_headroom; if (skb_cow_head(skb, dev->needed_headroom)) { dev->stats.tx_dropped++; dev_kfree_skb(skb); return ; } skb_dst_drop(skb); skb_dst_set(skb, &rt->dst); skb_push(skb, sizeof (struct iphdr)); skb_reset_network_header(skb); iph = ip_hdr(skb); inner_iph = (const struct iphdr *)skb_inner_network_header(skb); iph->version = 4 ; iph->ihl = sizeof (struct iphdr) >> 2 ; iph->frag_off = df; iph->protocol = tnl_params->protocol; iph->tos = ip_tunnel_ecn_encap(tos, inner_iph, skb); iph->daddr = fl4.daddr; iph->saddr = fl4.saddr; iph->ttl = ttl; __ip_select_ident(iph, skb_shinfo(skb)->gso_segs ?: 1 ); iptunnel_xmit(skb, dev); return ; …… }
iptunnel_xmit 1 2 3 4 5 6 7 8 9 10 11 12 static inline void iptunnel_xmit (struct sk_buff *skb, struct net_device *dev) { int err; int pkt_len = skb->len - skb_transport_offset(skb); struct pcpu_tstats *tstats = this_cpu_ptr(dev->tstats); nf_reset(skb); err = ip_local_out(skb); …… }
GRE实例 期望拓扑如下
环境信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # vm 1 [root@test-1 ~]# uname -sr Linux 3.10.0-693.el7.x86_64 [root@test-1 ~]# ip addr show …… 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1446 qdisc pfifo_fast state UP qlen 1000 link/ether fa:16:3e:0c:e3:c6 brd ff:ff:ff:ff:ff:ff inet 10.10.10.11/24 brd 10.10.10.255 scope global dynamic eth0 valid_lft 82685sec preferred_lft 82685sec # vm2 [root@test-2 net]# uname -sr Linux 3.10.0-693.el7.x86_64 [root@test-2 net]# ip addr show …… 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1446 qdisc pfifo_fast state UP qlen 1000 link/ether fa:16:3e:50:61:0d brd ff:ff:ff:ff:ff:ff inet 10.10.10.9/24 brd 10.10.10.255 scope global dynamic eth0 valid_lft 82645sec preferred_lft 82645sec
开始配置
首先需要给设备加载ip_gre模块 1 [root@test-1 ~]# modprobe ip_gre
加载完成之后,设备应该会多出两个接口:gre0、gretap0。这是模块初始过程中创建的两个针对不同gre类型的两个接口(first device)。
创建接口并设置IP。
1 2 3 [root@test-1 ~]# ip tunnel add gre1 mode gre remote 10.10.10.9 local 10.10.10.11 ttl 255 [root@test-1 ~]# ip link set gre1 up [root@test-1 ~]# ip addr add 192.168.1.3/24 peer 192.168.1.4/24 dev gre1
对端设备做类似配置。
路由表项。
1 2 [root@test-1 ~]# ip route 192.168.1.0/24 dev gre1 proto kernel scope link src 192.168.1.3
测试连通性。
对端抓包如下
troubleshooting ping 不通时,进行抓包,命令如下
1 2 # tcpdump -i eno1 ip[9]=47 # ip头第9(从0开始)位为protocol位,gre在linux中的支持为47。
两端都进行抓包,判断报文是否到达。
对端已经接收到,但是无回应:判断对端规则(iptables等)是否已经放开。 本端收到回应但ping依然不通:估计是本地规则问题。 优秀资料 GRE技术介绍