Linux下一代防火墙bpfilter是什么?让我演示给你看
昨晚为了解决公司的一个bug熬了个夜,等待期间花了半小时撸了一个bpfilter的简易POC,今早发了个朋友圈:
且看这个链接:https://lwn.net/Articles/747504
这个POC还是复杂了,而且标准发行版里根本就不可用,ko模块不可执行,依然是标准模块。
原理就这么简单,想证明可行性大可不必折腾大场面搞什么umh的(作者的意图显然是站技术实现的立场的,而我从业务角度考虑如何快速部署),昨晚花了半小时几条命令,四十来行代码就能说明问题。xdp的ebpf只能作为nf的cache存在,无状态,功能有限,全靠map关联状态,且关联到interface,创建完备的有状态流表还是需要慢速路径,完全替代nf当前是不可能的。其实Cisco从早期开始就用这种方式支持ACL了,都二十多了。
我的POC也仅仅支持无状态五元组匹配。
我想关于Linux bpfilter的中文描述,我又是第一人了。
bpfilter是什么?几乎没有什么资料介绍,这里有必要简述一番。
我们知道,Linux防火墙框架经历了ip firewall,ipchains,iptanles,nftables,越发完美,但是性能一直都是问题,毕竟不管任何框架,防火墙规则都是以某种 列表 的形式存在的,内核并没有对列表进行任何预处理,在内核协议栈的处理经路上顺序callback一堆list回调函数,这是问题的根源。
更加可恶的是,iptables规则不支持增量配置,因此无论是规则的增删改查,其时间复杂度均为O(n)O(n)O(n),看到这个O(n)O(n)O(n),程序员就怒了。
虽然nftables让规则list更加紧凑,但是执行逻辑依然繁琐,这个时候,ebpf来了。
我之所以对ebpf情有独钟,并不是因为我精通这个,我喜欢ebpf完全是因为早在2004年的时候,我就听说 “可以将Cisco/H3C的ACL规则‘编译’到接口” ,Cisco路由器交换机对ACL规则进行一定的预处理优化,编译加载到具体的网卡,这个思路很先进了。然后我发现ebpf也可以将ACL规则编译到XDP,这完全是情怀使然,与技术无关。
如今,Linux上iproute2套装也可以完成eBPF程序载入网卡了,这个iproute2套装我一用就是8年!我非常喜欢它的与时俱进!
ebpf程序会被编译成中间字节码,然后被JIT编译成智能网卡可以执行的指令序列(就像被JIT编译成x86_64指令一般)并且载入智能网卡的ram,成为其XDP的可执行程序,这就是其智能之所在。
大致的原理就是这样,但是原理说再多没个鸟用,问题的关键在我们该如何做才能玩起来。
链接 https://lwn.net/Articles/747504 所示的POC依赖一种叫做umh的机制,说白了就是个trick,该trick旨在解决以下的矛盾:
- iptables规则到JIT码的转换确实内核来负责。
- JIT编译过程非常复杂且策略化,不适合在内核完成。
于是就出现了umh。然而直到Ubuntu 19.10(内核版本5.3.0-23-generic),bpfilter依然处于 不可用 的状态,并且构建链接所述的POC执行环境非常繁琐。
即然如此,为什么不自己DIY一个呢?
让我们开始。
我的POC很简单,它完成下面的功能:
- 将用户配置的iptales规则实时翻译成ebpf字节码注入到某网卡的XDP(通用的,或者硬件offload的)。
为了完成这个,必须有一种机制可以监控iptables规则的变化,我找到了xtables-monitor程序,它事实上和iptables一样,也是xtables-multi的一个链接:
root@zhaoya-VirtualBox:/home/zhaoya# ls /usr/local/sbin/xtables-monitor -l
lrwxrwxrwx 1 root root 17 11月 27 11:31 /usr/local/sbin/xtables-monitor -> xtables-nft-multi
它可以监控规则的变化。不过这里有个问题,xtables-monitor只对iptables-nft兼容命令有效,无法监控传统iptables命令修改的规则,而bpfilter的目标之一就是兼容传统iptables工具套件的二进制ABI。不过这些对于做一个POC而言,问题不大。如果真的要做的话,就要对Linux内核本身做fix了,无非也就是添加几行代码的事儿。
接下来的任务是,我们要把xtables-monitor监控到的变化注入到一个程序中,该程序将这个变化反应到对应网卡的XDP eBPF程序上去。
这并不难,下面我用执果溯因的思路,一步步将代码贴出来,请看我们需要的结果:
root@zhaoya-VirtualBox:/samples/bpf# xtables-monitor -e |xargs -i ./bpf_filter {} /sys/fs/bpf/xdp/globals/action_map
命令无需解释,唯一要说的就是iptables套件对stdout有需求,所以必须将iptables源码树中所有的打印规则的函数printf换成dprintf到stdout。
我们现在需要看看bpf_filter程序的代码:
// bpf_filter_user.c
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <bpf/bpf.h>
#include <linux/bpf.h>
#include "bpf_util.h"#define SIZE 32static int action_map_fd;int main(int argc, char **argv)
{int o, i = 0, opt = 0, action;in_addr_t addr, *paddr;char buff[64], *token;char *optstring[32];const char s[2] = " ";char *mapfile;// 传入的正是xtables-monitor捕获的规则集的变化optstring[i++] = calloc(1, SIZE);strcpy(optstring[0], "EVENT:");sprintf(buff, "%s\n", strstr(argv[1], optstring[0]));mapfile = argv[2];token = strtok(buff, s);token = strtok(NULL, s);while (token != NULL) {optstring[i] = malloc(SIZE);strcpy(optstring[i++], token);token = strtok(NULL, s);}optstring[i] = NULL;while((o = getopt(i, optstring, "4t:A:D:s:j:")) != EOF) {switch (o) {case 'A':opt = 1; break;case 'D':opt = 0; break;case 's': {char *raw_addr = strtok(optarg, "/");addr = inet_addr(raw_addr);paddr = &addr;break;}case 'j':if (!strncmp(optarg, "DROP", 4)) {action = 1;} else if (!strncmp(optarg, "ACCEPT", 6)) {action = 0;}break;default: break;}}// 这是一个PIN住的全局map,我们打开它。action_map_fd = bpf_obj_get(mapfile);// 增加或者删除规则,只要体现在XDP的map上if (opt == 0) {bpf_map_delete_elem(action_map_fd, (const void *)paddr);printf("delete source IP:%x\n", addr);} else if (opt == 1) {bpf_map_update_elem(action_map_fd, (const void *)&addr, (const unsigned int *)&action, 0);printf("add/update source IP:%x\n", addr);}return 0;
}
还差一个代码,即eBPF代码本身了,现在就给出:
// bpf_filter_kern.c
#include <uapi/linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"#define PIN_GLOBAL_NS 2
struct bpf_elf_map {__u32 type;__u32 size_key;__u32 size_value;__u32 max_elem;__u32 flags;__u32 id;__u32 pinning;
};// 系统范围内的全局map
struct bpf_elf_map SEC("maps") action_map = {.type = BPF_MAP_TYPE_HASH,.size_key = sizeof(int),.size_value = sizeof(int),.pinning = PIN_GLOBAL_NS,.max_elem = 100,
};static inline int parse_ipv4(void *data, u64 nh_off, void *data_end,__be32 *src, __be32 *dest)
{struct iphdr *iph = data + nh_off;if (iph + 1 > data_end)return 0;*src = iph->saddr;*dest = iph->daddr;return iph->protocol;
}// 默认策略为ACCEPT的处理逻辑本身
SEC("xdp_action") // 注意iproute2的section字段
int xdp_drop_prog(struct xdp_md *ctx)
{void *data_end = (void *)(long)ctx->data_end;void *data = (void *)(long)ctx->data;int *action_entry = NULL;int in_index = ctx->ingress_ifindex, *out_index;__be32 src_ip = 0, dest_ip = 0;struct ethhdr *eth = (struct ethhdr *)data;u16 h_proto;u64 nh_off;u32 ipproto;nh_off = sizeof(*eth);if (data + nh_off > data_end) {return XDP_DROP;}h_proto = eth->h_proto;if (h_proto != htons(ETH_P_IP))return XDP_PASS;ipproto = parse_ipv4(data, nh_off, data_end, &src_ip, &dest_ip);action_entry = bpf_map_lookup_elem(&action_map, &src_ip);if (action_entry) {if (*action_entry == 0)return XDP_PASS;else if (*action_entry == 1)return XDP_DROP;}// Default policy PASSreturn XDP_PASS;
}char _license[] SEC("license") = "GPL";
把上面的eBPF程序编译成bpf_filter_kern.o的时候,代码工作就全部结束了。
是时候用一下它了。
我使用iproute2来将这个eBPF的字节码注入网卡enp0s8的XDP上,这已经非常类似Cisco处理ACL的做法了:
root@zhaoya-VirtualBox:~# ip --force link set dev enp0s8 xdp obj bpf_filter_kern.o sec xdp_action
# 上述命令如果需要将规则处理offload到硬件(前提是硬件要支持,比如Netronome NFP SmartNIC),就要用xdpoffload选项(而不是xdp)了
当以上命令执行完毕之后,效果就是在enp0s8网卡上多了XDP处理:
root@zhaoya-VirtualBox:~# ip link ls dev enp0s8
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric qdisc fq_codel state UP mode DEFAULT group default qlen 1000link/ether 08:00:27:ce:25:7e brd ff:ff:ff:ff:ff:ffprog/xdp id 13 tag e177e2c4d4c4914d jited
我们可以通过bpftool一窥究竟:
root@zhaoya-VirtualBox:~# bpftool p|tail -3
13: xdp tag e177e2c4d4c4914d gplloaded_at 2019-11-27T21:20:53+0800 uid 0xlated 264B jited 166B memlock 4096B map_ids 12
root@zhaoya-VirtualBox:~# bpftool map dump id 12
Found 0 elements
为了确认全局的PIN map已经生成,我们确认下面的文件已经建立:
root@zhaoya-VirtualBox:~# ll /sys/fs/bpf/xdp/globals/action_map
-rw------- 1 root root 0 11月 27 18:39 /sys/fs/bpf/xdp/globals/action_map
是时候测试一下效果了。
搭建以下的拓扑:
确认56.110能ping通被测试机器56.101。
此时在56.101上执行下面的命令:
xtables-monitor -e |xargs -i ./bpf_filter {} /sys/fs/bpf/xdp/globals/action_map
我们添加一条iptables规则:
iptables-nft -A INPUT -s 192.168.56.110 -j DROP
可以发现,ping不通了,此时为了确认确实是XDP DROP生效了而不是iptales规则本身生效了,我们可以使用tcpdump确认没有抓到任何数据包,同时bpftool显示,XDP的map确实有表项生成:
root@zhaoya-VirtualBox:~# bpftool map dump id 12
key: c0 a8 38 6e value: 01 00 00 00
Found 1 element
这就是被禁止的192.168.56.110的DROP策略,同时,我们可以看到注入到enp0s8网卡的eBPF代码:
root@zhaoya-VirtualBox:~# bpftool p |tail -3
13: xdp tag e177e2c4d4c4914d gplloaded_at 2019-11-27T21:20:53+0800 uid 0xlated 264B jited 166B memlock 4096B map_ids 12
root@zhaoya-VirtualBox:~# bpftool p d x i 130: (79) r2 = *(u64 *)(r1 +8)1: (79) r1 = *(u64 *)(r1 +0)2: (b7) r3 = 03: (63) *(u32 *)(r10 -4) = r34: (b7) r6 = 15: (bf) r3 = r16: (07) r3 += 147: (2d) if r3 > r2 goto pc+238: (71) r3 = *(u8 *)(r1 +12)9: (71) r4 = *(u8 *)(r1 +13)10: (67) r4 <<= 811: (4f) r4 |= r312: (b7) r6 = 213: (55) if r4 != 0x8 goto pc+1714: (bf) r3 = r115: (07) r3 += 3416: (2d) if r3 > r2 goto pc+217: (61) r1 = *(u32 *)(r1 +26)18: (63) *(u32 *)(r10 -4) = r119: (bf) r2 = r1020: (07) r2 += -421: (18) r1 = map[id:12]23: (85) call __htab_map_lookup_elem#10420824: (15) if r0 == 0x0 goto pc+125: (07) r0 += 5626: (15) if r0 == 0x0 goto pc+427: (61) r1 = *(u32 *)(r0 +0)28: (b7) r6 = 129: (15) if r1 == 0x1 goto pc+130: (b7) r6 = 231: (bf) r0 = r632: (95) exit
接下来,让我们删除iptables规则:
iptables-nft -D INPUT -s 192.168.56.110 -j DROP
map重新归为空:
root@zhaoya-VirtualBox:~# bpftool map dump id 12
Found 0 elements
两台机器继续相互可以ping通。所以说,XDP的eBPF规则完美生效。时隔很多年,Linux支持了XDP/eBPF之后,也终于可以像Cisco/H3C那般自我了。
这就是我的POC了。其实我在代码中已经留下了 “接口” ,比如getopt那块,我已经按照规范指引了正轨的代码应该怎么写,幸运的是,iptables规则本身就是argv格式的,确实不错。
浙江温州皮鞋湿,下雨进水不会胖。
Linux下一代防火墙bpfilter是什么?让我演示给你看相关推荐
- Check Point R81.10 - 下一代防火墙 (NGFW)
Check Point R81.10 - 下一代防火墙 (NGFW) Quantum Security Gateway and Gaia R81.10, the Release Notes, Reso ...
- python编写网络防火墙_dnxfirewall:一款纯Python实现的下一代防火墙系统
dnxfirewall dnxfirewall是一套包含了经过优化的高性能应用程序及服务套件,它可以将一个标准的Linux系统转换成一个基于空间区域的下一代防火墙系统.dnxfirewall中的所有软 ...
- 网康下一代防火墙RCE漏洞
使用需知 由于传播和使用本文所提供的任何直接或间接的后果和损失,均由用户承担,作者对此不承担任何责任.如果文章出现敏感内容产生不良影响,请联系作者删除. 一.漏洞描述 防火墙使用linux进行开发的, ...
- 山石网科发布智能下一代防火墙新版本 应对未知威胁
2月5日北京,民族网络安全领导厂商山石网科发布智能下一代防火墙新版本,总裁兼CEO罗东平亲自分享了山石网科在未知威胁防护方面取得的新成果:智能下一代防火墙将通过基于威胁行为的分析技术识别未知威胁,帮助 ...
- linux路由器转发效率,如何使用Intel 10 Gbe解决Linux路由器/防火墙转发性能问题?...
我们有一个 Linux防火墙,带有两个面向外部的10Gbe适配器(Intel 82599EB)和一个面向内部的10Gbe适配器(Intel 82598EB). 我遇到的问题是防火墙只会以非常低的速率转 ...
- linux关闭防火墙stop,linux如何关闭防火墙
我的linux不想开启防火墙了,想要关闭,该怎么办呢?下面由学习啦小编给你做出详细的linux关闭防火墙方法介绍!希望对你有帮助! linux关闭防火墙方法一: 重启后生效 开启: chkconfig ...
- 下一代防火墙NGFW解读
下一代防火墙NGFW应具备的六大功能 下一代防火墙需要安全厂商不断的关注IT环境和客户需求的变化.持续专注的技术积累及创新,而厚积薄发的产品成果.下一代防火墙应该实实在在实现以下六大功能:基于用户防护 ...
- 下一代防火墙的5个优点
现代网络攻击和先进的黑客攻击方法的复杂性正在推动企业寻求下一代防火墙以获得更好的安全性.新的基于Web的恶意软件和入侵企图绕过外围保护来利用应用程序.用户容易受到恶意电子邮件或网络钓鱼方案的影响,因为 ...
- Linux iptables防火墙设置与NAT服务配置
Linux iptables防火墙设置与NAT服务配置 - 摘要: linux教程,NAT服务器,iptables防火墙设置与NAT服务配置, 防火墙是指设置在不同网络或网络安全域之间的一系列部件的组 ...
最新文章
- Codeforces Round #470 (rated, Div. 2 C. Producing Snow(思维)
- 预测2019:数据中心将有哪些变化
- Tensorflow:TF模型文件(checkpoint文件夹下ckpt文件之data、index、meta)保存、模型导入、恢复并fine-tuning之详细攻略
- unity 如何获取到屏幕中间_Unity通用渲染管线Shader日志输出工具
- 在RHEL5下实现RAID5磁盘阵列
- Android之关于电话录音原理,目前的方法还是只能录MIC
- 数据结构课上笔记13
- LeetCode LCS 02. 完成一半题目(计数+排序)
- java 判断请求为 ajax请求_Java过滤器处理Ajax请求,Java拦截器处理Ajax请求,java 判断请求是不是ajax请求...
- Android花屏分析,Unity游戏在手机上运行时的花屏现象
- vscode如何运行python新手教程_从零开始的TensorFlow+VScode开发环境搭建的步骤(图文)...
- phper的何去何从
- iis启动服务时提示在本地计算机 无法启动iis admin服务,无法启动IIS Express Web服务器...
- 利用云终端减少硬件冗余 提高机房整体管理效率
- 简约竞聘个人简历自我介绍PPT模板
- 黄金矿工java实现
- NodeJS - 第一个应用程序Hello World
- Java抽象画--秒变绘图大师
- PHP制作个人名片二维码
- 如何给html文件加背景图片,怎么在文件夹中设置背景图片