周末用eBPF实现了学习型网桥的XDP快速转发路径之后,再来用eBPF实现一个快速路由转发。同样很有意思。

关于eBPF和XDP的前置基础知识,我在前面实现网桥转发路径前已经概览过了,所以本文不再赘述。


当我们面对各种网络技术的时候,路由和交换技术是最基本的。路由和交换有什么区别,为什么不能像实现交换机那样实现IP路由,很有必要说一说。

简单点来说,路由和交换的实现技术有以下的区别:

  • 交换:精确匹配交换表项的二层转发过程。
  • 路由:模糊通配路由表项的三层转发过程。

二层网络链路的scope是局域的,三层IP网络的scope则是全世界IP网络,如果我们需要用一张表来装二层或者三层的转发表项,那么很显然,二层网络的转发表项的数量在这张表的可承受范围之内,而三层网络由于总表项数巨大则必然需要某种通配技术,这就是IP路由的最长前缀匹配技术。

落实到实现上,二层转发表项精确匹配的特征很容易使其被固化于硬件中用于快速交换,而三层路由表项的通配查找则必然需要牵扯到高性能查找算法,这些很难用硬件固化,所以一般的高性能路由器均采用了数据面和控制面相分离的方案来实现。

以下的说法本质上都差不多:

  • 一次路由,多次交换。
  • Cisco CEF。
  • 路由器快速转发由线卡实现,而线卡未命中表项的报文则被CPU软执行路由查找,最终将表项注入线卡。

在XDP技术出现之前,我就用Netfilter的nf_conntrack实现过类似的想法,尝试过各种所谓的 “协议栈短路” 方案或者说trick,但都显得远远不够酷。现在有了XDP,又有了eBPF,是时候玩一把了。

我们先来看生成eBPF字节码并注入XDP的C代码:

// xdp_rtcache_kern.c
#include <uapi/linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/ip.h>#include "bpf_helpers.h"// 最简单的一个转发表项
struct rt_item {int ifindex; // 转发出去的接口char eth_source[ETH_ALEN]; // 封装帧的源MAC地址。char eth_dest[ETH_ALEN]; // 封装帧的目标MAC地址。
};// 路由转发表缓存
struct bpf_map_def SEC("maps") rtcache_map = {.type = BPF_MAP_TYPE_LRU_HASH, // 采用LRU机制,自动老化表项.key_size = sizeof(int),.value_size = sizeof(struct rt_item),.max_entries = 100,
};// 递减TTL还是要的
static __always_inline int ip_decrease_ttl(struct iphdr *iph)
{u32 check = (__force u32)iph->check;check += (__force u32)htons(0x0100);iph->check = (__force __sum16)(check + (check >= 0xFFFF));return --iph->ttl;
}// 字节码的C程序本身
SEC("xdp_rtcache")
int xdp_rtcache_prog(struct xdp_md *ctx)
{void *data_end = (void *)(long)ctx->data_end;void *data = (void *)(long)ctx->data;struct bpf_fib_lookup ifib;struct ethhdr *eth = data;struct iphdr *iph;struct rt_item *pitem = NULL;unsigned int daddr = 0;u16 h_proto;u64 nh_off;// 至今不知道如何让ebpf程序支持 "%s"char fast_info[] = "Fast path to [%d]\n";char slow_info[] = "Slow path to [%d]\n";nh_off = sizeof(*eth);if (data + nh_off > data_end) {return XDP_DROP;}__builtin_memset(&ifib, 0, sizeof(ifib));h_proto = eth->h_proto;if (h_proto != htons(ETH_P_IP)) {return XDP_PASS;}iph = data + nh_off;if (iph + 1 > data_end) {return XDP_DROP;}daddr = iph->daddr;pitem = bpf_map_lookup_elem(&rtcache_map, &daddr);// 首先精确查找转发表,如果找到就直接转发,不必再经历最长前缀匹配的慢速通配查找// 这个动作是可以offload到硬件中的。if (pitem) {ip_decrease_ttl(iph);memcpy(eth->h_dest, pitem->eth_dest, ETH_ALEN);memcpy(eth->h_source, pitem->eth_source, ETH_ALEN);bpf_trace_printk(fast_info, sizeof(fast_info), pitem->ifindex);return bpf_redirect(pitem->ifindex, 0);}// 否则只能执行最长前缀匹配了ifib.family = AF_INET;ifib.tos = iph->tos;ifib.l4_protocol = iph->protocol;ifib.tot_len = ntohs(iph->tot_len);ifib.ipv4_src = iph->saddr;ifib.ipv4_dst = iph->daddr;ifib.ifindex = ctx->ingress_ifindex;// 调用eBPF封装的路由查找函数,虽然所谓慢速查找,也依然不会进入协议栈的。if (bpf_fib_lookup(ctx, &ifib, sizeof(ifib), 0) == 0) {struct rt_item nitem;memset(&nitem, 0, sizeof(nitem));memcpy(&nitem.eth_dest, ifib.dmac, ETH_ALEN);memcpy(&nitem.eth_source, ifib.smac, ETH_ALEN);nitem.ifindex = ifib.ifindex;// 插入新的表项bpf_map_update_elem(&rtcache_map, &daddr, &nitem, BPF_ANY);ip_decrease_ttl(iph);memcpy(eth->h_dest, ifib.dmac, ETH_ALEN);memcpy(eth->h_source, ifib.smac, ETH_ALEN);bpf_trace_printk(slow_info, sizeof(slow_info), ifib.ifindex);return bpf_redirect(ifib.ifindex, 0);}return XDP_PASS;
}char _license[] SEC("license") = "GPL";

是不是很闭环的一个代码呢?这是不是意味着我们只需要将其 编译加载到对应的网卡(调用API和iproute2的ip-link命令均可以完成) 就可以了呢?并不是这样。

事实上,我们需要考虑更细节的情况。

如果路由改变了呢?如果ARP表项改变了呢?…

所以说,我们必须侦听这种事件。简单起见,我们只要监测到这种事件发生了,就清空转发表。以下是对应的用户控制C代码:

// xdp_rtcache_user.c#include <linux/bpf.h>
#include <linux/rtnetlink.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <bpf/bpf.h>
#include <poll.h>
#include <net/if.h>
#include "bpf_util.h"int sock, sock_arp, flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static int *ifindex_list;
char buf[1024];
static int rtcache_map_fd;static void int_exit(int sig)
{int i = 0;for (i = 0; i < 2; i++) {bpf_set_link_xdp_fd(ifindex_list[i], -1, flags);}exit(0);
}int main(int ac, char **argv)
{struct bpf_prog_load_attr prog_load_attr = {.prog_type    = BPF_PROG_TYPE_XDP,};struct bpf_object *obj;char filename[256];int prog_fd;int i = 1;struct pollfd fds_route, fds_arp;struct sockaddr_nl la, lr;struct nlmsghdr *nh;snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);prog_load_attr.file = filename;if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd))return 1;rtcache_map_fd = bpf_object__find_map_fd_by_name(obj, "rtcache_map");ifindex_list = (int *)calloc(2, sizeof(int *));ifindex_list[0] = if_nametoindex(argv[1]);ifindex_list[1] = if_nametoindex(argv[2]);for (i = 0; i < 2; i++) {bpf_set_link_xdp_fd(ifindex_list[i], prog_fd, flags);}signal(SIGINT, int_exit);signal(SIGTERM, int_exit);sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);memset(&lr, 0, sizeof(lr));lr.nl_family = AF_NETLINK;lr.nl_groups = RTMGRP_IPV6_ROUTE | RTMGRP_IPV4_ROUTE | RTMGRP_NOTIFY;bind(sock, (struct sockaddr *)&lr, sizeof(lr));fds_route.fd = sock;fds_route.events = POLL_IN;sock_arp = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);memset(&la, 0, sizeof(la));la.nl_family = AF_NETLINK;la.nl_groups = RTMGRP_NEIGH | RTMGRP_NOTIFY;bind(sock_arp, (struct sockaddr *)&la, sizeof(la));fds_arp.fd = sock_arp;fds_arp.events = POLL_IN;while (1) {memset(buf, 0, sizeof(buf));if (poll(&fds_route, 1, 3) == POLL_IN) {recv(sock, buf, sizeof(buf), 0);nh = (struct nlmsghdr *)buf;} else if (poll(&fds_arp, 1, 3) == POLL_IN) {recv(sock_arp, buf, sizeof(buf), 0);nh = (struct nlmsghdr *)buf;}if (nh->nlmsg_type == RTM_NEWNEIGH || nh->nlmsg_type == RTM_DELNEIGH ||nh->nlmsg_type == RTM_NEWROUTE || nh->nlmsg_type == RTM_DELROUTE) {__u32 id = 0, next_id;while (bpf_map_get_next_key(rtcache_map_fd, &id, &next_id) == 0) {bpf_map_delete_elem(rtcache_map_fd, &next_id);id = next_id;}}}
}

接下来让我们编译它们。

我依然建议使用内核源码树的samples/bpf编译目录,将上述两个文件放在该目录下,修改Makefile文件,加入以下的内容:

always += xdp_rtcache_kern.o
hostprogs-y += xdp_rtcache
xdp_rtcache-objs := xdp_rtcache_user.o

最后make之。

好了,是时候演示一下了。

首先,我们在不启用该eBPF的情况下,确认路由的畅通,在路由器出入网卡抓包显示数据包有来有回。保持ping的同时,加载eBPF字节码:

root@zhaoya-VirtualBox:# ./xdp_rtcache enp0s9 enp0s10

另起一个终端,观察调试输出:

root@zhaoya-VirtualBox:# cat /sys/kernel/debug/tracing/trace_pipe<idle>-0     [000] ..s. 26971.653936: 0: Slow path to [4]ksoftirqd/3-30    [003] ..s. 26971.654749: 0: Slow path to [5]<idle>-0     [000] .Ns. 26972.655742: 0: Fast path to [4]<idle>-0     [003] .Ns. 26972.657759: 0: Fast path to [5]...

OK,可以看到前面两个Slow报文是从双向ICMP报文学习而来的,此后就都走Fast路径了。此时tcpdump抓包,nothing will be shown。


浙江温州皮鞋湿,下雨进水不会胖。

实现基于XDP/eBPF的快速路由转发功能相关推荐

  1. Linux开启路由转发功能(透明代理环境搭建)

    环境: 客户端:         地址 -- 192.168.1.230          网关 -- 192.168.1.56 代理服务器: eth0 -- 192.168.1.56         ...

  2. linux开启内部路由转发功能

    linux开启内部路由转发功能 2015-09-23 18:02:09 标签:linux开启内部路由转么功能 linux内部机器共享上网 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 ...

  3. 开启Linux系统路由转发功能 实现多网段电脑共享上网

    开启Linux系统路由转发功能 实现多网段电脑共享上网 作者:佚名 字体:[增加 减小] 来源:互联网 时间:07-13 13:03:29 我要评论 目前,Linux的主要应用在服务器领域,但是它的许 ...

  4. 苹果电脑macbook/mac_os开启路由转发功能

    mac OS开启路由转发功能 主要是我有时候需要用mac进行ps4直播转发,需要mac开启路由功能 环境:MAC OS 10.11 机器:macbook pro 配置方法 基本步骤跟linux类似,不 ...

  5. 防火墙(13)——实现路由转发功能(1)

    首先我们要准备三台虚拟机: 我的第一台当做客户端:ip为192.168.19.180(NAT模式) 我的第二台当做转发功能端:首先得配置两个网卡,网卡的配置在我前面的博客中有涉及到,ip分别为192. ...

  6. Linux开启路由转发功能

    标记一下,今天想让一台Red Hat Enterprise Linux 7开通iptables的nat转发功能,找了半天. A服务器:192.168.30.20/24 B服务器:192.168.30. ...

  7. 防火墙(14)——实现路由转发功能(2)

    在刚才的主机上,我已经安装好了httpd,可以通过:netstat -tunlp查看到: 已经可以服务,接下来我向/var/www/html/下放入index.html,这样httpd服务的默认首页就 ...

  8. BFE原生路由转发功能分析

    为什么加上"原生",因为我们基于BFE开发已经魔改了. 路由转发是BFE作为一个七层流量代理服务的核心功能,BFE设计了一套支持多租户.多机房的路由转发模型.  (图片来源:< ...

  9. 树莓派4b乌班图服务器系统,【树莓派4B】8、树莓派软路由,Ubuntu18.04配置路由转发...

    个人目的是经过把树莓派作成一个软路由,至关于一个智能路由器.html 材料 树莓派4B,操做系统其实能够是其余的Linux版本.觉得4B自带无线网卡,就用自带无线网卡做为路由出口.个人系统是Ubunt ...

最新文章

  1. 深度复制_Python 列表切片陷阱:引用、复制与深复制
  2. “天猫化”的考拉,还是考拉吗?
  3. drupal 覆写登录框
  4. Java终于可以加入字符串
  5. 02 unix文件系统和命令
  6. 孩子数学成绩不好怎么办_我孩子数学成绩不好,怎么办
  7. 【转】tensorflow中的batch_norm以及tf.control_dependencies和tf.GraphKeys.UPDATE_OPS的探究
  8. mysql生成uui mybatis,MyBatis自动生成UUID并返回
  9. 使用jQuery美化英雄联盟
  10. google广告分类
  11. 2022年虎年新春致地球上全体读者朋友们的一封书信
  12. color-thief-php提取图片色值分布及百分占比
  13. 裸看美剧必备英文词汇文章索引
  14. NC15979 小q的数列
  15. 投影仪怎么看电视直播?安装这些软件带你共享2022世界杯盛宴
  16. Docker 下载安装 Docker 配置镜像加速器
  17. 求星期算法c语言程序,C语言根据指定日期计算是星期几
  18. JSON和XML的区别
  19. IOS 后台运行 播放音乐
  20. 数字IC设计工程师的知识结构(转载学习)

热门文章

  1. Spark 报错Incompatible equality constraint: String and T2
  2. Icon Status CDS视图 添加信号灯状态 Fiori Element- Criticality
  3. Hypergraph Neural Networks HGNN
  4. M1 Mac安装 PS 2021 闪退、卡启动界面解决方法
  5. 鲤鱼溪传说:神鱼与三仙姑
  6. Android手机直播
  7. 53个全球免费学术资源数据库整理,查资料写论文必备【开学必备】
  8. Mapped Statements collection does not contain value for 解决方法
  9. codeforces 702F 可持久化平衡树
  10. python算三角形面积怎么样保留两位小数_通过坐标计算三角形面积