上一篇文章阅读量比较多,看起来网络的主题比较受欢迎。这一篇文章我们继续探索 BPF
在网络领域的应用:使用 BPF
来实现安全组。
按照腾讯云的文档,安全组的概念如下:
安全组是一种虚拟防火墙,具备有状态的数据包过滤功能,用于设置云服务器、负载均衡、云数据库等实例的网络访问控制,控制实例级别的出入流量,是重要的网络安全隔离手段。
在本文中,我们将实现如下的规则:
0.0.0.0/0:10216 ---> TCP:12160 0.0.0.0/0:* -x-> TCP:12160
即允许所有源端口为 10216
的 TCP
流量访问服务端 12160
端口,通过其他端口访问的流量都丢弃(本文仅讨论入站流量的过滤,当然,对出站流量的限制也是可行的)。
XDP
是 eXpress Data Path
的缩写,在 Linux
内核中为 BPF
提供了一个框架,用于实现高性能的可编程的数据包处理。它在整个软件栈的起始点,即网络驱动程序收到以太网帧的时刻运行 BPF
程序。
回到本文的主题,我们通过把安全组规则翻译成 BPF
程序,利用 XDP
挂载至网卡设备上执行,即可达成目标。
实现上述功能的 BPF
程序如下:
#include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/tcp.h> #include "bpf_helpers.h" #include "bpf_endian.h" static int is_secure_source(void *data_begin, void *data_end) { struct ethhdr *eth_header = data_begin; if ((void *)(eth_header + 1) > data_end) { return 1; } if (eth_header->h_proto != bpf_htons(ETH_P_IP)) { return 1; } struct iphdr *ip_header = (struct iphdr *)(eth_header + 1); if ((void *)(ip_header + 1) > data_end) { return 1; } if (ip_header->protocol != IPPROTO_TCP) { return 1; } struct tcphdr *tcp_header = (struct tcphdr *)(ip_header + 1); if ((void *)(tcp_header + 1) > data_end) { return 1; } if (tcp_header->dest == bpf_htons(12160)) { if (tcp_header->source != bpf_htons(10216)) { return 0; // reject } else { return 1; // accept } } else { return 1; } } SEC("xdp") int xdp_secure_policy(struct xdp_md *ctx) { void *data = (void *)(__u64)ctx->data; void *data_end = (void *)(__u64)ctx->data_end; if (is_secure_source(data, data_end)) { return XDP_PASS; } else { return XDP_DROP; } } char __license[] SEC("license") = "GPL";
程序的功能是,对于网卡收到的每一个数据,依次跳过合法的以太网帧首部,IP数据报首部,最后查看 TCP 报文首部的目的端口是否是 12160
,若是,则进一步判断源端口是否是 10216
,以此决定是否允许入站流量。在整个程序中,对于指针边界的判断是必需的,若缺失,会导致程序不能通过内核 BPF
验证器的校验。(在程序中,我们对不认识/不完整的数据都予以放过)最后编译为二进制文件 sg.bpf.o
。
另外我们还需要一个简单的服务端程序来验证功能:
package main import ( "io" "log" "net" ) func serve(c net.Conn) { defer c.Close() log.Printf("client connected: %s\n", c.RemoteAddr().String()) io.Copy(c, c) log.Printf("client closed: %s\n", c.RemoteAddr().String()) } func main() { lis, err := net.Listen("tcp", ":12160") if err != nil { panic(err) } for { conn, err := lis.Accept() if err != nil { panic(err) } go serve(conn) } }
程序的功能很简单,监听在 12160
端口,对于每一个连接上的客户端, echo
客户端的输入。最后编译为可执行程序 testserver
。
在加载 BPF
程序之前,我们先运行测试用的服务端程序,
$ ./testserver
然后从另一台主机上连接到这个服务上,在两个不同的终端,分别执行如下命令:
$ nc $(SERVER_IP) 12160 -p 10216 hello hello
$ nc $(SERVER_IP) 12160 hi hi
可以看到,两个客户端都能正常访问服务端,现在,我们加载上述的 BPF
程序:
$ sudo ip link set dev eth0 xdpgeneric obj sg.bpf.o sec xdp verbose
即把 BPF
程序加载到 eth0
网卡上(这里操作模式选择了 xdpgeneric
,因为实验环境的虚拟机不支持 xdpdrv
/xdpoffload
)。
现在,再次尝试在两个终端发送数据到服务端:
$ nc $(SERVER_IP) 12160 -p 10216 hello hello hey hey
$ nc $(SERVER_IP) 12160 hi hi no reply
表现符合预期。源端口为 10216
的客户端仍然能将数据发送给服务端并接收响应,其他客户端则一直在重传,直至服务端重置连接。
本文的代码可以在这里找到。
本文探讨了使用 XDP 和 BPF 实现自定义安全组,通过可编程的方式实现了对入站流量的访问控制。
基于阿里巴巴的互联网架构、大数据技术,利用混合云架构打造全新的云化电子税 务...
前言 微服务成了互联网架构的标配模式,对微服务之间的调用的流量治理和管控就尤...
1.百度是个大骗子,我抄了十几年的满分作文却从未得过满分。 2.学神在刷难题,...
创业与投资的本质,都是追寻一种能够穿越时空,抵达未来的高效方式。 德勤管理咨...
本文转载自微信公众号「后端Q」,作者conan。转载本文请联系后端Q公众号。 概述 ...
3月24日,腾讯发布2020年Q4及全年财报,其中金融科技及企业服务第四季收入385亿...
作者 | 楚奕 来源 | 阿里技术公众号 这篇文章主要从技术视角介绍下跨平台WebCanv...
1.某女生寝室门口贴着一个告示男生与饭盒不得入内,问何解?答曰两者都会搞大女...
背景 有时候我会碰到快速搭建测试服务的需求,比如像这样: 搭建一个 HTTP Servi...
1.在报名的路上,我看见远处的学校,轰!的一声没了。希望如此。 2.男:我一直...