一文读懂eBPF/XDP
XDP概述
XDP是Linux网络路径上内核集成的数据包处理器,具有安全、可编程、高性能的特点。当网卡驱动程序收到数据包时,该处理器执行BPF程序。XDP可以在数据包进入协议栈之前就进行处理,因此具有很高的性能,可用于DDoS防御、防火墙、负载均衡等领域。
XDP数据结构
XDP程序使用的数据结构是xdp_buff,而不是sk_buff,xdp_buff可以视为sk_buff的轻量级版本。
两者的区别在于:sk_buff包含数据包的元数据,xdp_buff创建更早,不依赖与其他内核层,因此XDP可以更快的获取和处理数据包。
xdp_buff数据结构定义如下:
// /linux/include/net/xdp.h
struct xdp_rxq_info {
struct net_device *dev;
u32 queue_index;
u32 reg_state;
struct xdp_mem_info mem;
} ____cacheline_aligned; /* perf critical, avoid false-sharing */
struct xdp_buff {
void *data;
void *data_end;
void *data_meta;
void *data_hard_start;
unsigned long handle;
struct xdp_rxq_info *rxq;
};
sk_buff数据结构定义如下:
// /include/linux/skbuff.h
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
union {
struct net_device *dev;
/* Some protocols might use this space to store information,
* while device pointer would be NULL.
* UDP receive path is one user.
*/
unsigned long dev_scratch;
};
};
struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack */
struct list_head list;
};
union {
struct sock *sk;
int ip_defrag_offset;
};
union {
ktime_t tstamp;
u64 skb_mstamp_ns; /* earliest departure time */
};
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48] __aligned(8);
union {
struct {
unsigned long _skb_refdst;
void (*destructor)(struct sk_buff *skb);
};
struct list_head tcp_tsorted_anchor;
};
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
unsigned long _nfct;
#endif
unsigned int len,
data_len;
__u16 mac_len,
hdr_len;
/* Following fields are _not_ copied in __copy_skb_header()
* Note that queue_mapping is here mostly to fill a hole.
*/
__u16 queue_mapping;
/* if you move cloned around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define CLONED_MASK (1 << 7)
#else
#define CLONED_MASK 1
#endif
#define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset)
__u8 __cloned_offset[0];
__u8 cloned:1,
nohdr:1,
fclone:2,
peeked:1,
head_frag:1,
xmit_more:1,
pfmemalloc:1;
#ifdef CONFIG_SKB_EXTENSIONS
__u8 active_extensions;
#endif
/* fields enclosed in headers_start/headers_end are copied
* using a single memcpy() in __copy_skb_header()
*/
/* private: */
__u32 headers_start[0];
/* public: */
/* if you move pkt_type around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_TYPE_MAX (7 << 5)
#else
#define PKT_TYPE_MAX 7
#endif
#define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset)
__u8 __pkt_type_offset[0];
__u8 pkt_type:3;
__u8 ignore_df:1;
__u8 nf_trace:1;
__u8 ip_summed:2;
__u8 ooo_okay:1;
__u8 l4_hash:1;
__u8 sw_hash:1;
__u8 wifi_acked_valid:1;
__u8 wifi_acked:1;
__u8 no_fcs:1;
/* Indicates the inner headers are valid in the skbuff. */
__u8 encapsulation:1;
__u8 encap_hdr_csum:1;
__u8 csum_valid:1;
#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_VLAN_PRESENT_BIT 7
#else
#define PKT_VLAN_PRESENT_BIT 0
#endif
#define PKT_VLAN_PRESENT_OFFSET() offsetof(struct sk_buff, __pkt_vlan_present_offset)
__u8 __pkt_vlan_present_offset[0];
__u8 vlan_present:1;
__u8 csum_complete_sw:1;
__u8 csum_level:2;
__u8 csum_not_inet:1;
__u8 dst_pending_confirm:1;
#ifdef CONFIG_IPV6_NDISC_NODETYPE
__u8 ndisc_nodetype:2;
#endif
__u8 ipvs_property:1;
__u8 inner_protocol_type:1;
__u8 remcsum_offload:1;
#ifdef CONFIG_NET_SWITCHDEV
__u8 offload_fwd_mark:1;
__u8 offload_l3_fwd_mark:1;
#endif
#ifdef CONFIG_NET_CLS_ACT
__u8 tc_skip_classify:1;
__u8 tc_at_ingress:1;
__u8 tc_redirected:1;
__u8 tc_from_ingress:1;
#endif
#ifdef CONFIG_TLS_DEVICE
__u8 decrypted:1;
#endif
#ifdef CONFIG_NET_SCHED
__u16 tc_index; /* traffic control index */
#endif
union {
__wsum csum;
struct {
__u16 csum_start;
__u16 csum_offset;
};
};
__u32 priority;
int skb_iif;
__u32 hash;
__be16 vlan_proto;
__u16 vlan_tci;
#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)
union {
unsigned int napi_id;
unsigned int sender_cpu;
};
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;
#endif
union {
__u32 mark;
__u32 reserved_tailroom;
};
union {
__be16 inner_protocol;
__u8 inner_ipproto;
};
__u16 inner_transport_header;
__u16 inner_network_header;
__u16 inner_mac_header;
__be16 protocol;
__u16 transport_header;
__u16 network_header;
__u16 mac_header;
/* private: */
__u32 headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
unsigned int truesize;
refcount_t users;
#ifdef CONFIG_SKB_EXTENSIONS
/* only useable after checking ->active_extensions != 0 */
struct skb_ext *extensions;
#endif
};
XDP与eBPF的关系
XDP程序是通过bpf()系统调用控制的,bpf()系统调用使用程序类型BPF_PROG_TYPE_XDP进行加载。
XDP操作模式
XDP支持3种工作模式,默认使用native模式:
Native XDP:在native模式下,XDP BPF程序运行在网络驱动的早期接收路径上(RX队列),因此,使用该模式时需要网卡驱动程序支持。Offloaded XDP:在Offloaded模式下,XDP BFP程序直接在NIC(Network Interface Controller)中处理数据包,而不使用主机CPU,相比native模式,性能更高Generic XDP:Generic模式主要提供给开发人员测试使用,对于网卡或驱动无法支持native或offloaded模式的情况,内核提供了通用的generic模式,运行在协议栈中,不需要对驱动做任何修改。生产环境中建议使用native或offloaded模式
XDP操作结果码
- XDP_DROP:丢弃数据包,发生在驱动程序的最早RX阶段
- XDP_PASS:将数据包传递到协议栈处理,操作可能为以下两种形式:
1、正常接收数据包,分配愿数据sk_buff结构并且将接收数据包入栈,然后将数据包引导到另一个CPU进行处理。他允许原始接口到用户空间进行处理。 这可能发生在数据包修改前或修改后。
2、通过GRO(Generic receive offload)方式接收大的数据包,并且合并相同连接的数据包。经过处理后,GRO最终将数据包传入“正常接收”流 - XDP_TX:转发数据包,将接收到的数据包发送回数据包到达的同一网卡。这可能在数据包修改前或修改后发生
- XDP_REDIRECT:数据包重定向,XDP_TX,XDP_REDIRECT是将数据包送到另一块网卡或传入到BPF的cpumap中
- XDP_ABORTED:表示eBPF程序发生错误,并导致数据包被丢弃。自己开发的程序不应该使用该返回码

XDP和iproute2加载器
iproute2工具中提供的ip命令可以充当XDP加载器的角色,将XDP程序编译成ELF文件并加载他。
- 编写XDP程序xdp_filter.c,程序功能为丢弃所有TCP连接包,程序将
xdp_md结构指针作为输入,相当于驱动程序xdp_buff的BPF结构。程序的入口函数为filter,编译后ELF文件的区域名为mysection。
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#define SEC(NAME) __attribute__((section(NAME), used))
SEC("mysection")
int filter(struct xdp_md *ctx) {
int ipsize = 0;
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
struct iphdr *ip;
ipsize = sizeof(*eth);
ip = data + ipsize;
ipsize += sizeof(struct iphdr);
if (data + ipsize > data_end) {
return XDP_DROP;
}
if (ip->protocol == IPPROTO_TCP) {
return XDP_DROP;
}
return XDP_PASS;
}
- 将XDP程序编译为ELF文件
clang -O2 -target bpf -c xdp_filter.c -o xdp_filter.o
- 使用ip命令加载XDP程序,将mysection部分作为程序的入口点
sudo ip link set dev ens33 xdp obj xdp_filter.o sec mysection
没有报错即完成加载,可以通过以下命令查看结果:
$ sudo ip a show ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric/id:56 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:2f:a8:41 brd ff:ff:ff:ff:ff:ff
inet 192.168.136.140/24 brd 192.168.136.255 scope global dynamic noprefixroute ens33
valid_lft 1629sec preferred_lft 1629sec
inet6 fe80::d411:ff0d:f428:ce2a/64 scope link noprefixroute
valid_lft forever preferred_lft forever
其中,xdpgeneric/id:56说明使用的驱动程序为xdpgeneric,XDP程序id为56
- 验证连接阻断效果
- 使用
nc -l 8888监听8888 TCP端口,使用nc xxxxx 8888连接发送数据,目标主机未收到任何数据,说明TCP连接阻断成功 - 使用
nc -kul 9999监听UDP 9999端口,使用nc -u xxxxx 9999连接发送数据,目标主机正常收到数据,说明UDP连接不受影响
- 卸载XDP程序
$ sudo ip link set dev ens33 xdp off
卸载后,连接8888端口,发送数据,通信正常。
XDP和BCC
编写C代码xdp_bcc.c,当TCP连接目的端口为9999时DROP:
#define KBUILD_MODNAME "program"
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
int filter(struct xdp_md *ctx) {
int ipsize = 0;
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
struct iphdr *ip;
ipsize = sizeof(*eth);
ip = data + ipsize;
ipsize += sizeof(struct iphdr);
if (data + ipsize > data_end) {
return XDP_DROP;
}
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)ip + sizeof(*ip);
ipsize += sizeof(struct tcphdr);
if (data + ipsize > data_end) {
return XDP_DROP;
}
if (tcp->dest == ntohs(9999)) {
bpf_trace_printk("drop tcp dest port 9999\n");
return XDP_DROP;
}
}
return XDP_PASS;
}
与使用ip命令加载XDP程序类似,这里编写python加载程序实现对XDP程序的编译和内核注入。
#!/usr/bin/python
from bcc import BPF
import time
device = "ens33"
b = BPF(src_file="xdp_bcc.c")
fn = b.load_func("filter", BPF.XDP)
b.attach_xdp(device, fn, 0)
try:
b.trace_print()
except KeyboardInterrupt:
pass
b.remove_xdp(device, 0)
验证效果,使用nc测试,无法与目标主机9999端口实现通信
$ sudo python xdp_bcc.py
<idle>-0 [003] ..s. 22870.984559: 0: drop tcp dest port 9999
<idle>-0 [003] ..s. 22871.987644: 0: drop tcp dest port 9999
<idle>-0 [003] ..s. 22872.988840: 0: drop tcp dest port 9999
<idle>-0 [003] ..s. 22873.997261: 0: drop tcp dest port 9999
<idle>-0 [003] ..s. 22875.000567: 0: drop tcp dest port 9999
<idle>-0 [003] ..s. 22876.002998: 0: drop tcp dest port 9999
<idle>-0 [003] ..s. 22878.005414: 0: drop tcp dest port 9999
<idle>-0 [003] ..s. 22882.018119: 0: drop tcp dest port 9999
参考
https://duo.com/labs/tech-notes/writing-an-xdp-network-filter-with-ebpf
https://davidlovezoe.club/wordpress/archives/937
一文读懂eBPF/XDP的更多相关文章
- 一文读懂HTTP/2及HTTP/3特性
摘要: 学习 HTTP/2 与 HTTP/3. 前言 HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何 ...
- 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现
一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...
- 一文读懂高性能网络编程中的I/O模型
1.前言 随着互联网的发展,面对海量用户高并发业务,传统的阻塞式的服务端架构模式已经无能为力.本文(和下篇<高性能网络编程(六):一文读懂高性能网络编程中的线程模型>)旨在为大家提供有用的 ...
- 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路
本文原作者阮一峰,作者博客:ruanyifeng.com. 1.引言 HTTP 协议是最重要的互联网基础协议之一,它从最初的仅为浏览网页的目的进化到现在,已经是短连接通信的事实工业标准,最新版本 HT ...
- 一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm)
一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm) 2017-12-25 16:29:19 对于 A3C 算法感觉自己总是一知半解,现将其梳理一下,记录在此,也 ...
- [转帖]MerkleDAG全面解析 一文读懂什么是默克尔有向无环图
MerkleDAG全面解析 一文读懂什么是默克尔有向无环图 2018-08-16 15:58区块链/技术 MerkleDAG作为IPFS的核心数据结构,它融合了Merkle Tree和DAG的优点,今 ...
- [转帖]一文读懂 HTTP/2
一文读懂 HTTP/2 http://support.upyun.com/hc/kb/article/1048799/ 又小拍 • 发表于:2017年05月18日 15:34:45 • 更新于:201 ...
- [转帖]从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路
从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路 http://www.52im.net/thread-1709-1-2.html 本文原作者阮一峰,作者博客:r ...
- 一文读懂HDMI和VGA接口针脚定义
一文读懂HDMI和VGA接口针脚定义 摘自:http://www.elecfans.com/yuanqijian/jiekou/20180423666604.html HDMI概述 HDMI是高清 ...
随机推荐
- linux之docker 安装 mysql
首先进入docker : 命令:systemctl start docker 查詢一下docker的状态: 命令:docker images 现在开始安装mysql了,第一步拉取镜像 命令:doc ...
- 使用Tomcat30分钟搭建个人服务器
目录 一.服务器简介 二.安装教程 三.出现的问题 一.服务器简介 Tomcat服务器是一个Apache软件资金会的开源项目,实现了Servlet,JSP,EL,WebSocket协议. 二.安装教程 ...
- 《C++ Primer》笔记 第13章 拷贝控制
拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么.拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么.析构函数定义了当此类型对象销毁时做什么.我们称这些操作为拷贝控制 ...
- monkey稳定性测试的步骤及策略
1.adb的作用是什么?adb的全称:android debug bridge 安卓调试桥梁,包含在 Android SDK 平台工具软件包中.通过该命令与设备进行通信,以便进行调试adb可以同时管理 ...
- Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析
下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...
- HDOJ-2222(AC自动机+求有多少个模板串出现在文本串中)
Keywords Search HDOJ-2222 本文是AC自动机的模板题,主要是利用自动机求有多少个模板出现在文本串中 由于有多组输入,所以每组开始的时候需要正确的初始化,为了不出错 由于题目的要 ...
- 翻译:《实用的Python编程》04_02_Inheritance
目录 | 上一节 (4.1 类) | 下一节 (4.3 特殊方法) 4.2 继承 继承(inheritance)是编写可扩展程序程序的常用手段.本节对继承的思想(idea)进行探讨. 简介 继承用于特 ...
- 漫漫Java路1—基础知识—初涉java
前言 主学信息安全,在编程的路上还是一个孩子,还在一步一步探索,有些东西可能是站在自己的位置思考的,很可能会出现一些啼笑皆非的错误,如果有误,还希望各位斧正. Java安装 jdk的安装 甲骨文官网选 ...
- 安装JDK9,jemter无法正常启动,怎么退回到JDK8
安装JDK8,配置环境变量 java -version显示的是8.1 然后安装JDK9之后,java -version显示的是9+8.1 这个时候,无法正常启动jemter 在环境变量中把path的C ...
- Android应用程序的进程创建过程
目录 前言 步骤 step1 Ams发起请求startProcessLocked step2 Zygote收到请求 step3 handleChildProc -- 进入子进程的世界 step4 Ru ...