XDP概述

XDP是Linux网络路径上内核集成的数据包处理器,具有安全、可编程、高性能的特点。当网卡驱动程序收到数据包时,该处理器执行BPF程序。XDP可以在数据包进入协议栈之前就进行处理,因此具有很高的性能,可用于DDoS防御、防火墙、负载均衡等领域。

XDP数据结构

XDP程序使用的数据结构是xdp_buff,而不是sk_buffxdp_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

  • 验证连接阻断效果
  1. 使用nc -l 8888监听8888 TCP端口,使用nc xxxxx 8888连接发送数据,目标主机未收到任何数据,说明TCP连接阻断成功
  2. 使用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的更多相关文章

  1. 一文读懂HTTP/2及HTTP/3特性

    摘要: 学习 HTTP/2 与 HTTP/3. 前言 HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何 ...

  2. 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现

    一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...

  3. 一文读懂高性能网络编程中的I/O模型

    1.前言 随着互联网的发展,面对海量用户高并发业务,传统的阻塞式的服务端架构模式已经无能为力.本文(和下篇<高性能网络编程(六):一文读懂高性能网络编程中的线程模型>)旨在为大家提供有用的 ...

  4. 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    本文原作者阮一峰,作者博客:ruanyifeng.com. 1.引言 HTTP 协议是最重要的互联网基础协议之一,它从最初的仅为浏览网页的目的进化到现在,已经是短连接通信的事实工业标准,最新版本 HT ...

  5. 一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm)

    一文读懂 深度强化学习算法 A3C (Actor-Critic Algorithm) 2017-12-25  16:29:19   对于 A3C 算法感觉自己总是一知半解,现将其梳理一下,记录在此,也 ...

  6. [转帖]MerkleDAG全面解析 一文读懂什么是默克尔有向无环图

    MerkleDAG全面解析 一文读懂什么是默克尔有向无环图 2018-08-16 15:58区块链/技术 MerkleDAG作为IPFS的核心数据结构,它融合了Merkle Tree和DAG的优点,今 ...

  7. [转帖]一文读懂 HTTP/2

    一文读懂 HTTP/2 http://support.upyun.com/hc/kb/article/1048799/ 又小拍 • 发表于:2017年05月18日 15:34:45 • 更新于:201 ...

  8. [转帖]从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路   http://www.52im.net/thread-1709-1-2.html     本文原作者阮一峰,作者博客:r ...

  9. 一文读懂HDMI和VGA接口针脚定义

    一文读懂HDMI和VGA接口针脚定义 摘自:http://www.elecfans.com/yuanqijian/jiekou/20180423666604.html   HDMI概述 HDMI是高清 ...

随机推荐

  1. 中心化-ESB

    服务调用者与服务提供者通过企业服务总线相连接: ESB成为瓶颈:无论在性能上还是成本消耗上,ESB都会导致瓶颈出现.

  2. vs2019远程调试

    VS2019远程调试 这几天遇到个很是纠结的问题,同样的源代码,放在测试服务器,完美运行.但是上线正式环境就是死活显示不出来.于是想到了微软的远程调试功能,这里用VS2019举例. 下载远程访问工具 ...

  3. 05.从0实现一个JVM语言之目标平台代码生成-CodeGenerator

    从0实现JVM语言之目标平台代码生成-CodeGenerator 源码github仓库, 如果这个系列文章对你有帮助, 希望获得你的一个star 本节相关代码生成package地址 阶段性的告别 非常 ...

  4. 源码解析之 Mybatis 对 Integer 参数做了什么手脚?

    title: 源码解析之 Mybatis 对 Integer 参数做了什么手脚? date: 2021-03-11 updated: 2021-03-11 categories: Mybatis 源码 ...

  5. 利用matplotlib和cmaps根据已有的colormap,重新定义colormap

    算法网上这哥们总结的还可以[1] ,但是使用matplotlib自定义colormap自己掌握的还不够,写在这里 希望达到的目标 使用什么样的颜色,可以自己定义 方便的调用其他人的色标, 使用一部分c ...

  6. 攻防世界 reverse Mysterious

    Mysterious  BUUCTF-2019 int __stdcall sub_401090(HWND hWnd, int a2, int a3, int a4) { char v5; // [e ...

  7. docker使用常见问题解决方案:错误号码2058,docker WARNING :IPv4,容器间的通讯

    1.错误号码2058 1,错误解决: 解决方法:docker下mysql容器 登录 mysql -u root -p 登录你的 mysql 数据库,然后 执行这条SQL: ALTER USER 'ro ...

  8. 「HTML+CSS」--自定义加载动画【010】

    前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...

  9. 02-MySQL主要配置文件

    一.二进制日志log-bin 作用:主从复制 二.错误日志 log-err 默认关闭,记录严重的警告和错误信息,每次启动和关闭的详细信息 三.慢查询日志log 默认关闭,记录查询的sql语句,如果开启 ...

  10. 【3.0 递归 Recursion 02】

    [递归:阶乘] 1.寻找基本情况 对于阶乘而言,最基本的情况就是0!和1!,二者的结果都是1 我们不妨现在方法中写下这个情况,帮助我们跳出递归 if(i<=1){ return 1 ; } 接下 ...