基于eBPF实现对GRE keepalive包的回复
最近我把一些跑在公有云上的RouterOS替换成了正常的Linux发行版
GRE Keepalive协议解析
GRE是一个非常简单的无状态隧道协议
虽然它是一个点对点隧道,但是它支持multicast
虽然它的包头比IPIP6和IP6IP等简单粗暴的隧道要大一点儿,但是它支支持MPLS之类的协议
无状态,不需要握手和协商,方便硬件(ASIC)实现封包和拆包
被绝大多数企业级路由器(黑盒子)支持,甚至可能是某些企业级路由器上唯一支持的隧道协议
所以在网络工程上,GRE的应用相当广泛。但是无状态这个特性在实践中会带来一些问题,比如很多路由器系统对静态路由是只支持根据接口的up和down状态实现路由failover的,GRE接口配置完以后永远处于up状态,就会给容灾架构的设计带来很大困扰。BFD协议可以用来检测隧道对端是否可达,但是也要和动态路由协议联动才有意义。要是有一种方法能让GRE隧道自己知道自己是否可以连通对端设备,然后改变自己的端口状态,那它就能完美符合一个路由器对接口这一概念的抽象了。GRE keepalive就是在这样的想法之下诞生的。
在企业环境下
Linux为什么不能原生支持GRE Keepalive
Linux的IP协议栈会对所有进来的包先做一些基础的检查以确定这个包是否合法
/* Given (packet source, input interface) and optional (dst, oif, tos):
* - (main) check, that source is valid i.e. not broadcast or our local
* address.
* - figure out what "logical" interface this packet arrived
* and calculate "specific destination" address.
* - check, that packet arrived from expected physical interface.
* called with rcu_read_lock()
*/
static int __fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst,
u8 tos, int oif, struct net_device *dev,
int rpf, struct in_device *idev, u32 *itag)
{
struct net *net = dev_net(dev);
struct flow_keys flkeys;
int ret, no_addr;
struct fib_result res;
struct flowi4 fl4;
bool dev_match;
fl4.flowi4_oif = 0;
fl4.flowi4_iif = l3mdev_master_ifindex_rcu(dev);
if (!fl4.flowi4_iif)
fl4.flowi4_iif = oif ? : LOOPBACK_IFINDEX;
fl4.daddr = src;
fl4.saddr = dst;
fl4.flowi4_tos = tos;
fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
fl4.flowi4_tun_key.tun_id = 0;
fl4.flowi4_flags = 0;
fl4.flowi4_uid = sock_net_uid(net, NULL);
no_addr = idev->ifa_list == NULL;
fl4.flowi4_mark = IN_DEV_SRC_VMARK(idev) ? skb->mark : 0;
if (!fib4_rules_early_flow_dissect(net, skb, &fl4, &flkeys)) {
fl4.flowi4_proto = 0;
fl4.fl4_sport = 0;
fl4.fl4_dport = 0;
}
if (fib_lookup(net, &fl4, &res, 0))
goto last_resort;
if (res.type != RTN_UNICAST &&
(res.type != RTN_LOCAL || !IN_DEV_ACCEPT_LOCAL(idev)))
goto e_inval;
fib_combine_itag(itag, &res);
dev_match = fib_info_nh_uses_dev(res.fi, dev);
/* This is not common, loopback packets retain skb_dst so normally they
* would not even hit this slow path.
*/
dev_match = dev_match || (res.type == RTN_LOCAL &&
dev == net->loopback_dev);
if (dev_match) {
ret = FIB_RES_NHC(res)->nhc_scope >= RT_SCOPE_HOST;
return ret;
}
if (no_addr)
goto last_resort;
if (rpf == 1)
goto e_rpf;
fl4.flowi4_oif = dev->ifindex;
ret = 0;
if (fib_lookup(net, &fl4, &res, FIB_LOOKUP_IGNORE_LINKSTATE) == 0) {
if (res.type == RTN_UNICAST)
ret = FIB_RES_NHC(res)->nhc_scope >= RT_SCOPE_HOST;
}
return ret;
last_resort:
if (rpf)
goto e_rpf;
*itag = 0;
return 0;
e_inval:
return -EINVAL;
e_rpf:
return -EXDEV;
}
这样一来
Linux上的GRE Keepalive实现
实现的思路其实很简单
从图上可以看到
XDP_PASS 表示这个包会按照流程继续走下去
XDP_DROP 表示这个包应该被丢掉
( XDP_TX 表示这个包应该被原路发回
XDP_REDIRECT 表示这个包应该被发送到其它接口上
XDP_ABORT 表示程序出错,无法处理(包会被丢掉)
另外
刚开始写eBPF程序的时候
Linux的隧道分成TAP和TUN两个大类