Skip to content

Commit

Permalink
update XDP_Res-Application
Browse files Browse the repository at this point in the history
  • Loading branch information
byxzone committed Aug 14, 2022
1 parent d6c2fdb commit efaa05a
Show file tree
Hide file tree
Showing 19 changed files with 648 additions and 0 deletions.
2 changes: 2 additions & 0 deletions eBPF_Supermarket/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@
| 基于 eBPF 的云原生场景下 Pod 性能监测 | cilium_ebpf_probe | 针对云原生领域下的Pod提供进程级别的细粒度监测 | 汪雨薇 |
| 基于 eBPF 的云原生场景下 sidecar 性能监测 | sidecar | 针对云原生场景下 sidecar 的工作原理进行内核层面的观测和优化 | [@ESWZY](https://github.com/ESWZY) |
| 基于 eBPF 的Linux系统性能监测工具-网络子系统 | Linux network subsystem monitoring based on eBPF | 基于eBPF机制对Linux系统网络子系统的TCP层进行性能监测 | [@AnneY](https://github.com/AnneYang720) |
| 基于 eBPF 的 XDP 研究与应用 | Research and application of XDP based on eBPF | 基于eBPF XDP机制打造一个工具使系统性能得到提升 | [@byxzone](https://github.com/byxzone) |

2 changes: 2 additions & 0 deletions eBPF_Supermarket/XDP_Res-Application/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode
./src/__pycache__
189 changes: 189 additions & 0 deletions eBPF_Supermarket/XDP_Res-Application/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
## 基于 eBPF 的 XDP 研究与应用

XDP 提供了一个内核态下高性能的可编程包处理框架,可在最早可以处理包的位置(即网卡驱动收到包的时刻)运行 BPF 程序,其具有非常优秀的数据面处理性能,打通了 Linux 网络处理的高速公路。本题目要求基于eBPF 和 XDP 进行研究,完成的内容包括:(1)分析 eBPF XDP实现的基本原理。(2)对比 XDP 和其它 Kernel Bypass 方案的优缺点,找出其适合的应用场景。(3)针对该应用场景进行编程设计,并可达到性能提升或安全性提升的效果。(4)可能的情况下,尝试将该工具部署在华为的鸿蒙系统上并进行测试。(加分项)

### XDP基本原理

XDP 可在最早可以处理包的位置(即网卡驱动收到包的时刻)运行 BPF 程序,并且暴露了一个可以加载 BPF 程序的网络钩子。在这个钩子中,程序能够对传入的数据包进行判别修改并快速决策,避免了内核内部处理带来的额外开销。
XDP 程序运行在内核网络协议栈之前,一个数据包经过网络协议栈的处理会产生相当大的开销,所以 XDP 提供了几种基本的能力,包括:

- XDP_DROP

丢弃且不处理数据包。eBPF 程序可以分析流量模式并使用过滤器实时更新 XDP 应用程序以丢弃特定类型的数据包(例如,恶意流量)。

- XDP_PASS

指示应将数据包转发到正常网络堆栈以进行进一步处理。XDP 程序可以在此之前修改包的内容。

- XDP_TX

将数据包(可能已被修改)转发到接收它的同一网络接口。

- XDP_REDIRECT

绕过正常的网络堆栈并通过另一个 NIC 将数据包重定向到网络。

关于XDP的其它内容,可以参考`./docs/xdp`目录其它文档:

XDP与题目应用背景:[backgroup.md](./docs/xdp/backgroud.md)

XDP基础内容:[xdp_basic.md](./docs/xdp/xdp_basic.md)

XDP与Kernel Bypass方案对比:[compare.md](./docs/xdp/compare.md)


### XDP iptables

#### 背景

netfilter/iptables 是 Linux 中内置的防火墙,其可以根据指定规则进行包过滤、重定向等功能。但是随着网络吞吐量的高速增长,netfilter/iptables 存在着很大的性能瓶颈,导致服务出现不可预测的延迟和性能下降。netfilter 框架在 IP 层,报文需要经过链路层,IP 层才能被处理,如果是需要丢弃报文,会白白浪费很多资源,影响整体性能,并且 netfilter 框架是一种可自由添加策略规则专家系统,并没有对添加规则进行合并优化,随着规模的增大,逐条匹配的机制也会影响性能。

利用 XDP 可以代替 netfilter/iptables 实现部分包过滤、重定向功能。XDP 可在最早可以处理包的位置运行 BPF 程序,根据预先设置的策略执行相应的动作,避免进入网络协议栈产生不必要的开销,从而提高系统的性能。根据测算,利用 XDP 技术的丢包速率要比 iptables 高 4 倍左右[1]。并且,通过改造匹配策略,借助 BPF HASH MAP ,可以进一步提升性能。

#### netfilter/iptables介绍与实现机制

netfilter/iptables 是采用数据包过滤机制工作的,它会对请求的数据包的包头进行分析,并根据预先设定的规则进行匹配来决定是否可以进入主机。它是一层一层过滤的,按照配置规则的顺序从上到下,从前到后进行过滤。iptables/netfilter 使用表来组织规则,根据用来做什么类型的判断标准,将规则分为不同表。在每个表内部,规则被进一步组织成链,内置的链是由内置的 hook 触发的。链基本上能决定规则何时被匹配。netfilter 在内核协议栈的各个重要关卡埋下了五个钩子。每一个钩子都对应是一系列规则,以链表的形式存在,所以俗称五链。当网络包在协议栈中流转到这些关卡的时候,就会依次执行在这些钩子上注册的各种规则,进而实现对网络包的各种处理。

关于 netfilter/iptables 以及其四表五链的其它内容,可以参考`./docs/iptables_netfilter`目录:

netfilter/iptables 具体介绍:[basic.md](./docs/iptables_netfilter/basic.md)

netfilter/iptables 内核实现:[kernel_implement.md](./docs/iptables_netfilter/kernel_implement.md)

#### 使用XDP提取五元组信息

在接收到包之后,可以通过计算偏移量,对数据包逐层解析的方式获取到五元组信息(传输层协议、源ip地址、目的ip地址、源端口号、目的端口号)。

```c
//data为数据包头指针,data_end为数据包结束位置指针
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
//偏移量
int offset = 0;
//存储五元组信息
struct metainfo info;
//以太网头部
struct ethhdr *eth = (struct ethhdr *)data;
//ip头部
struct iphdr *ip;
//以太网头部偏移量
offset = sizeof(struct ethhdr);
//通过数据包头+偏移量的方式得到ip头部
ip = data + offset;
//从ip头部获取信息
info.ipproto = ip->protocol;//协议
info.saddr = ip->saddr;//源地址
info.daddr = ip->daddr;//目的地址
//再次计算偏移量
offset += sizeof(struct iphdr);
if(info.ipproto == IPPROTO_TCP){
//tcp头部
struct tcphdr *tcp = data + offset;
offset += sizeof(struct tcphdr);
if(data + offset > data_end)
return XDP_DROP;
//从tcp头部获取信息
info.sport = tcp->source;//源端口
info.dport = tcp->dest;//目的端口
}
else if(info.ipproto == IPPROTO_UDP){
//udp头部
struct udphdr *udp = data + offset;
offset += sizeof(struct udphdr);
if(data + offset > data_end)
return XDP_DROP;
//从udp头部获取信息
info.sport = udp->source;//源端口
info.dport = udp->dest;//目的端口
}
```

#### 规则匹配

参考字节跳动的匹配方案[1],实现了规则匹配。

```c
//存储各项规则的BPF HASH MAP
BPF_HASH(ipproto_map, u32, u32);
BPF_HASH(saddr_map, u32, u32);
BPF_HASH(daddr_map, u32, u32);
BPF_HASH(sport_map, u16, u32);
BPF_HASH(dport_map, u16, u32);
BPF_HASH(action_map,u32, u32);

static int match_rule(struct metainfo *info){
int result_bit = 0;
//查找对应规则
int *ipproto_bit = ipproto_map.lookup(&info->ipproto);
int *saddr_bit = saddr_map.lookup(&info->saddr);
int *daddr_bit = daddr_map.lookup(&info->daddr);
int *sport_bit = sport_map.lookup(&info->sport);
int *dport_bit = dport_map.lookup(&info->dport);
if(ipproto_bit != NULL){ //是否匹配到对应规则
if(*ipproto_bit != 0){ //0代表没有
if(result_bit == 0){ //result_bit为空时,使result_bit等于该项规则编号
result_bit = *ipproto_bit;
}
else
result_bit = result_bit & *ipproto_bit; //如果result_bit不为空,与该规则编号进行按位与运算。
}
}
if(saddr_bit != NULL){
if(*saddr_bit != 0){
if(result_bit == 0)
result_bit = *saddr_bit;
else
result_bit = result_bit & *saddr_bit;
}
}
if(daddr_bit != NULL){
if(*daddr_bit != 0){
if(result_bit == 0)
result_bit = *daddr_bit;
else
result_bit = result_bit & *daddr_bit;
}
}
if(sport_bit != NULL){
if(*sport_bit != 0){
if(result_bit == 0)
result_bit = *sport_bit;
else
result_bit = result_bit & *sport_bit;
}
}
if(dport_bit != NULL){
if(*dport_bit != 0){
if(result_bit == 0)
result_bit = *dport_bit;
else
result_bit = result_bit & *dport_bit;
}
}
if(result_bit == 0) //如果result_bit仍未空,说明没有匹配到规则,执行XDP_PASS(即什么都不做)
return XDP_PASS;
//执行到这说明匹配到了规则,进一步处理
result_bit &= -result_bit; //得到优先级最高的规则编号,等价于 result_bit &= !result_bit + 1
//从action表里查找对应动作
int *action = action_map.lookup(&result_bit);
if(action != NULL)
return *action; //返回对应动作

return XDP_PASS; //没有找到相应动作,返回XDP_PASS
}
```
该规则匹配方案仍然存在一些问题,在实际运行过程中出现误处理,仍在改进中。
#### 运行
```
cd src
sudo python3 ./filter.py
```
目前自定义规则部分仍在编写,现有程序的规则是写在`rules.py`里的。
### Ref
[1] [字节跳动技术团队 —— eBPF技术实践:高性能ACL](https://blog.csdn.net/ByteDanceTech/article/details/106632252)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
## iptables

iptables是使 用很广泛的防火墙工具之一,它基于内核的包过滤框架 netfilter。

### iptables工作流程
iptables是采用数据包过滤机制工作的,所以它会对请求的数据包的包头进行分析,并根据我们预先设定的规则进行匹配来决定是否可以进入主机。并且

① 防火墙是一层一层过滤的。实际是按照配置规则的顺序从上到下,从前到后进行过滤的;
② 如果匹配上规则,即明确表明阻止还是通过,此时数据包就不再向下匹配新规则了;
③ 如果所有规则中没有明确表明是阻止还是通过这个数据包,也就是没有匹配上规则,则按照默认策略进行处理;
④ 防火墙的默认规则是对应的链的所有的规则执行完成后才会执行的;

### iptables四表五链

#### 表与链的对应关系

||| 说明 |
| ------- | ----------- | -------------------------------------------------------- |
| Filter | INPUT | 对于指定到本地套接字的包,即到达本地防火墙服务器的数据包 |
| Filter | FORWARD | 路由穿过的数据包,即经过本地防火墙服务器的数据包 |
| Filter | OUTPUT | 本地创建的数据包 |
| NAT | PREROUTING | 一进来就对数据包进行改变 |
| NAT | OUTPUT | 本地创建的数据包在路由之前进行改变 |
| NAT | POSTROUTING | 在数据包即将出去时改变数据包信息 |
| Managle | INPUT | 进入到设备本身的包 |
| Managle | FORWARD | 对路由后的数据包信息进行修改 |
| Managle | PREROUTING | 在路由之前更改传入的包 |
| Managle | OUTPUT | 本地创建的数据包在路由之前进行改变 |
| Managle | POSTROUTING | 在数据包即将离开时更改数据包信息

其中,Filter表为默认表、NAT表为当遇到新创建的数据包连接时参考、Managle表专门用于改变数据包。

规则链名包括(也被称为五个钩子函数):

- INPUT链 :处理输入数据包。
- OUTPUT链 :处理输出数据包。
- FORWARD链 :处理转发数据包。
- PREROUTING链 :用于目标地址转换(DNAT)。
- POSTOUTING链 :用于源地址转换(SNAT)。

#### 作用

| filter表 | |
| -------- | -------------------------------------- |
| INPUT | 负责过滤所有目标地址是本机地址的数据包 |
| FORWARD | 负责转发流经主机的数据包 |
| OUTPUT | 处理所有本机地址的数据包 |

| filter表 | |
| ----------- | ------------------------------------------------------------ |
| OUTPUT | 改变主机发出数据包的目标地址 |
| PREROUTING | 在数据包到达防火墙时,进行路由判断之前执行的规则,作用是改变数据包的目标地址、目的端口等 |
| POSTROUTING | 在数据包离开防火墙时进行路由判断之后的规则,作用是改变数据包的源地址、源端口等 |


### iptables原理

iptables 与协议栈内有包过滤功能的 hook 交 互来完成工作。这些内核 hook 构成了 netfilter 框架。

每个进入网络系统的包(接收或发送)在经过协议栈时都会触发这些 hook,程序可以通过注册 hook 函数的方式在一些关键路径上处理网络流量。iptables 相关的内核模块在这些 hook 点注册了处理函数,因此可以通过配置 iptables 规则来使得网络流量符合防火墙规则。

netfilter 提供了 5 个 hook 点。包经过协议栈时会触发**内核模块注册在这里的处理函数** 。触发哪个 hook 取决于包的方向(是发送还是接收)、包的目的地址、以及包在上一个 hook 点是被丢弃还是拒绝等等。

下面几个 hook 是内核协议栈中已经定义好的:

- `NF_IP_PRE_ROUTING`: 接收到的包进入协议栈后立即触发此 hook,在进行任何路由判断 (将包发往哪里)之前
- `NF_IP_LOCAL_IN`: 接收到的包经过路由判断,如果目的是本机,将触发此 hook
- `NF_IP_FORWARD`: 接收到的包经过路由判断,如果目的是其他机器,将触发此 hook
- `NF_IP_LOCAL_OUT`: 本机产生的准备发送的包,在进入协议栈后立即触发此 hook
- `NF_IP_POST_ROUTING`: 本机产生的准备发送的包或者转发的包,在经过路由判断之后, 将触发此 hook

iptables 使用表来组织规则,根据用来做什么类型的判断标准,将规则分为不同表。在每个表内部,规则被进一步组织成链,内置的 链 是由内置的hook触发的。链基本上能决定规则何时被匹配。

内置的链名字和 netfilter hook 名字是一一对应的:

- `PREROUTING`: 由 `NF_IP_PRE_ROUTING` hook 触发
- `INPUT`: 由 `NF_IP_LOCAL_IN` hook 触发
- `FORWARD`: 由 `NF_IP_FORWARD` hook 触发
- `OUTPUT`: 由 `NF_IP_LOCAL_OUT` hook 触发
- `POSTROUTING`: 由 `NF_IP_POST_ROUTING` hook 触发

链使管理员可以控制在包的传输路径上哪个点应用策略。因为每个表有多个链,因此一个表可以在处理过程中的多 个地方施加影响。特定类型的规则只在协议栈的特定点有意义,因此并不是每个表都 会在内核的每个 hook 注册链。

<img src="./images/arch.png">

<img src="./images/overview.png">

Ref

https://arthurchiao.art/blog/deep-dive-into-iptables-and-netfilter-arch-zh/

https://mp.weixin.qq.com/s/ZyZ_VpsewX5b2E-fwtGgsg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
netfilter 在内核协议栈的各个重要关卡埋下了五个钩子。每一个钩子都对应是一系列规则,以链表的形式存在,所以俗称五链。当网络包在协议栈中流转到这些关卡的时候,就会依次执行在这些钩子上注册的各种规则,进而实现对网络包的各种处理。

### 接收过程

在网络包接收在 IP 层的入口函数是 ip_rcv。

```c
int ip_rcv(struct sk_buff *skb, ......){
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);

}
```
`NF_HOOK` 函数会执行到 iptables 中 pre_routing 里的各种表注册的各种规则。当处理完后,进入 `ip_rcv_finish`。在这里函数里将进行路由选择。
```c
static int ip_rcv_finish(struct sk_buff *skb){
...
if (!skb_dst(skb)) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
...
}
...
return dst_input(skb);
}
```

如果发现是本地设备上的接收,会进入 `ip_local_deliver` 函数。接着是又会执行到 LOCAL_IN 钩子,也就是INPUT 链。

```c
int ip_local_deliver(struct sk_buff *skb){
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);

}
```
接收数据的处理流程是:PREROUTING链 -> 路由判断(是本机)-> INPUT链 -> ...
![image-20220804002231023](images/image-20220804002231023.png)
### 转发过程
当转发数据包时,先是经历接收数据的前半段。在 ip_rcv 中经过 PREROUTING 链,然后路由后发现不是本设备的包,那就进入 ip_forward 函数进行转发,在这里又会遇到 FORWARD 链。最后还会进入 ip_output 进行真正的发送,遇到 POSTROUTING 链。
![image-20220804002851493](images/image-20220804002851493.png)
### Ref
https://mp.weixin.qq.com/s/ZyZ_VpsewX5b2E-fwtGgsg
17 changes: 17 additions & 0 deletions eBPF_Supermarket/XDP_Res-Application/docs/xdp/applications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## XDP应用分析

### 使用XDP实现轻量级高性能防火墙

netfilter/iptables 是 Linux 中内置的防火墙,其可以根据指定规则进行包过滤、重定向等功能。但是随着网络吞吐量的高速增长,netfilter/iptables 存在着很大的性能瓶颈,导致服务出现不可预测的延迟和性能下降。

netfilter 框架在 IP 层,报文需要经过链路层,IP 层才能被处理,如果是需要丢弃报文,会白白浪费很多资源,影响整体性能,并且 netfilter 框架是一种可自由添加策略规则专家系统,并没有对添加规则进行合并优化,这严重依赖操作人员技术水平,随着规模的增大,规则数量 n 成指数级增长,而报文处理又是 0(n)复杂度,最终性能会直线下降。

可以利用 XDP 打造一个轻量的高性能防火墙,利用 XDP 的底层优势,可以在数据包进入网络协议栈之前对数据包进行判别。根据用户定义的规则,选择对数据包进行丢包、转发等操作,并且可以利用 BPF Map 等探索一种时间复杂度更低的匹配方式,达到减少不必要的资源消耗,提升整体性能的效果。

### 使用AF_XDP改进应用程序的网络性能

AF_XDP和AF_INET,AF_PACKET一样,属于address family的一种,AF_XDP socket 通过 XDP 直接将网卡收到的数据包重定向到用户空间,绕过内核的网络协议栈,能够用来实现高性能的网络包处理。

AF_XDP 可以看作是 XDP 的用户态接口,应用程序需要事先将定制的 XDP程序绑定到对应的网卡上,XDP 程序在内核网卡驱动中会预先处理网卡收到的网络报文,它会将报文发送到一个在用户态可以读写的共享内存(UMEM)当中,应用程序可以直接利用 AF_XDP socket 来接收数据,在 UMEM 中完成对网络报文的读写。

可以利用 AF_XDP 探索一种改进应用程序网络性能的方法,比如数据零拷贝,可以和 XDP 的转发结合(XDP 可重定向至 AF_XDP 类型的 socket),达到性能提升的效果。
Loading

0 comments on commit efaa05a

Please sign in to comment.