我之前写过一篇 介绍 tunnel 的文章 ,只是作了大致的介绍。里面多数 tunnel 是很容易理解的,由于它们可能是一对一的,换句话说,是直接从一端到另外一端。好比 IPv6 over IPv4 的 tunnel,也就是 SIT,它的原理以下图所示:html
显然,除了端点的 host A 和 host B以外,中间通过的任何设备都是看不到里面的 IPv6 的头,对于它们来讲,通过 sit 发出的包和其它的 IPv4 的包没有任何区别。网络
GRE tunnel 却不同了,它的原理从根本上和 sit,ipip 这样的 tunnel 就不同。除了外层的 IP 头和内层的 IP 头之间多了一个 GRE 头以外,它最大的不一样是, tunnel 不是创建在最终的 host 上,而是在中间的 router 上 !换句话说,对于端点 host A 和 host B 来讲,该 tunnel 是透明的(对比上面的 sit tunnel)。这是网上不少教程里没有直接告诉你的。理解这一点很是关键,正是由于它这么设计的,因此它才能解决 ipip tunnel 解决不了的问题。函数
因此,通过 GRE tunnel 发送的包(从 host A 发送到 host B)大致过程是这样子的(仍然借用了 LARTC 中的例子 ):ui
咱们能够看出,从 host A 发出的包其实就是一个很普通的 IP 包,除了目的地址不直接可达外。该 GRE tunnel 的一端是创建在 router A上,另外一段是创建在 router B上,因此添加外部的 IP 头是在 router A 上完成的,而去掉外面的 IP 头是在 router B上完成的,两个端点的 host 上几乎什么都不用作(除了配置路由,把发送到 10.0.2.0 的包路由到 router A)!.net
这么设计的好处也就很容易看出来了,ipip tunnel 是端对端的,通讯也就只能是点对点的,而 GRE tunnel 却能够进行多播。设计
如今让咱们看看Linux内核是怎么实现的,咱们必须假设 router A 和 B 上都是运行的 Linux,而 host A 和 B上运行什么是无所谓的。指针
发送过程是很简单的,由于 router A 上配置了一条路由规则,凡是发往 10.0.2.0 网络的包都要通过 netb 这个 tunnel 设备,在内核中通过 forward 以后就最终到达这个 GRE tunnel 设备的 ndo_start_xmit(),也就是 ipgre_tunnel_xmit() 函数。这个函数所作的事情无非就是经过 tunnel 的 header_ops 构造一个新的头,并把对应的外部 IP 地址填进去,最后发送出去。router
稍微难理解的是接收过程,即 router B 上面进行的操做。这里须要指出的一点是,GRE tunnel 本身定义了一个新的 IP proto,也就是 IPPROTO_GRE。当 router B 收到从 router A 过来的这个包时,它暂时还不知道这个是 GRE 的包,它首先会把它看成普通的 IP 包处理。由于外部的 IP 头的目的地址是该路由器的地址,因此它本身会接收这个包,把它交给上层,到了 IP 层以后才发现这个包不是 TCP,UDP,而是 GRE,这时内核会转交给 GRE 模块处理。htm
GRE 模块注册了一个 hook:blog
因此真正处理这个包的函数是 ipgre_rcv() 。ipgre_rcv() 所作的工做是:经过外层IP 头找到对应的 tunnel,而后剥去外层 IP 头,把这个“新的”包从新交给 IP 栈去处理,像接收到普通 IP 包同样。到了这里,“新的”包处理和其它普通的 IP 包已经没有什么两样了:根据 IP 头中目的地址转发给相应的 host。注意:这里我所谓的“新的”包其实并不新,内核用的仍是同一个copy,只是skbuff 里相应的指针变了。
以上就是Linux 内核对 GRE tunnel 的整个处理过程。另外,GRE 的头以下所示(图片来自 这里 ):
IP header in GRE tunnel
GRE header
转自:http://www.tuicool.com/articles/VNzY7n