diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router1.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router1.png new file mode 100644 index 000000000..15a3adc3f Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router1.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router2.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router2.png new file mode 100644 index 000000000..552456079 Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router2.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router3.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router3.png new file mode 100644 index 000000000..2cc22a450 Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router3.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router4.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router4.png new file mode 100644 index 000000000..d72f5b9b7 Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/router4.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap1.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap1.png new file mode 100644 index 000000000..3f90754ea Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap1.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap2.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap2.png new file mode 100644 index 000000000..65388ee2e Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap2.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap3.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap3.png new file mode 100644 index 000000000..4679137f6 Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap3.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap4.png b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap4.png new file mode 100644 index 000000000..9d5fb67db Binary files /dev/null and b/eBPF_Supermarket/Network_Subsystem/net_manager/document/image/socketmap4.png differ diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/ip_filter.md b/eBPF_Supermarket/Network_Subsystem/net_manager/document/ip_filter.md index 63d339e63..731bf9c4f 100644 --- a/eBPF_Supermarket/Network_Subsystem/net_manager/document/ip_filter.md +++ b/eBPF_Supermarket/Network_Subsystem/net_manager/document/ip_filter.md @@ -6,8 +6,46 @@ ### 实现 +其具体的代码架构如下图所示 + ![image-20240726104526418](./image/ip_filter1.png) +核心代码逻辑如下: + +```c +static int match_rules_ipv4_loop(__u32 index, void *ctx) +{ + int i = 0; + unsigned char *saddr; + unsigned char *daddr; + struct match_rules_loop_ctx *p_ctx = (struct match_rules_loop_ctx *)ctx; + if(index != p_ctx->next_rule) + return 0; + + struct rules_ipv4 *p_r = bpf_map_lookup_elem(&rules_ipv4_map, &index); + if(!p_r){ + return 1; //out of range + } + + p_ctx->next_rule = p_r->next_rule; + + if(index == 0) + goto out_match_rules_ipv4_loop; + bpf_printk("match_rules_ipv4_loop %d",index); + if( ipv4_cidr_match(p_ctx->conn->saddr, p_r->saddr, p_r->saddr_mask) && + ipv4_cidr_match(p_ctx->conn->daddr, p_r->daddr, p_r->daddr_mask) && + port_match(p_ctx->conn->sport, p_r->sport) && + port_match(p_ctx->conn->dport, p_r->dport) && + port_match(p_ctx->conn->ip_proto, p_r->ip_proto) ) + { + p_ctx->action = p_r->action; + ... + return 1; + } +``` + +​ 其通过加载预先配置好的文件,将其读取到对应map中,在接收到报文时,使用规则逐条对该报文进行匹配,其中包括精准匹配和泛化匹配(CIDR),最终,将我们给出的决策结果赋给XDP行为,使其进行具体的逻辑操作。 + #### 输入参数优化 在原先的黑白名单中,名单的路径参数十分固定 diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/mac_filter.md b/eBPF_Supermarket/Network_Subsystem/net_manager/document/mac_filter.md index ec11fc7ec..2d59e0981 100644 --- a/eBPF_Supermarket/Network_Subsystem/net_manager/document/mac_filter.md +++ b/eBPF_Supermarket/Network_Subsystem/net_manager/document/mac_filter.md @@ -79,7 +79,7 @@ sudo ./netmanager -d ens33 -S --progname=xdp_entry_mac -m conf.d/mac_load.conf - 00:00:00:00:00:00 00:00:00:00:00:00 DENY ``` -我们还对某一厂商的MAC地址进行泛化匹配,当前三字节不为0(固定厂商)且后三字节为0时,可以对其进行泛化,匹配到所有改厂商的MAC地址,如 +我们还对某一厂商的MAC地址进行泛化匹配,当前三字节不为0(固定厂商)且后三字节为0时,可以对其进行泛化,匹配到所有该厂商的MAC地址,如 ``` 00:0c:29:00:00:00 00:00:00:00:00:00 ALLOW diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/router.md b/eBPF_Supermarket/Network_Subsystem/net_manager/document/router.md new file mode 100644 index 000000000..2190d4ebb --- /dev/null +++ b/eBPF_Supermarket/Network_Subsystem/net_manager/document/router.md @@ -0,0 +1,92 @@ +## 路由优化 + +### 概述 + +**XDP 技术通过高效的数据包处理实现了路由优化,专注于网络层面的快速包转发和流量管理。** XDP在内核层直接处理数据包,绕过传统的网络协议栈,从而显著降低延迟和提高数据转发效率。通过在数据包到达协议栈之前对其进行处理,XDP 能够根据实时路由信息和流量策略快速做出转发决策,优化网络性能。 + +其主要应用在于: + +1. **高效路由**: XDP 在数据包到达协议栈之前进行处理,可以快速查找并应用路由规则,从而减少传统路由查找的延迟。通过内存中的路由缓存和快速前缀匹配,XDP 能够显著提高路由决策速度,优化网络流量的处理效率。 +2. **减小延迟**: 由于 XDP 处理的数据包是在网络协议栈之前,避免了传统网络栈中可能发生的额外处理步骤,从而减少了数据包的处理延迟。这种低延迟特性特别适用于对实时性要求高的应用,如高频交易或视频流传输。 +3. **动态路由更新**: XDP 允许动态更新路由信息并立即生效。通过与 eBPF 程序结合,可以实时响应网络状态变化,例如链路状态或路由变化,从而保持路由信息的及时性和准确性,增强网络的灵活性和鲁棒性。 +4. **负载均衡和流量控制**: XDP 可以与流量控制和负载均衡策略结合使用,通过对流量的高效处理和路由优化,确保流量在网络中得到合理分配和高效转发。这有助于防止网络瓶颈和提升整体网络性能。 + +**XDP 的路由优化功能主要体现在提升数据包转发效率和减少处理延迟**,使得网络设备能够在高负载条件下保持高性能。虽然 XDP 主要在数据包层面进行优化,但与现有的路由协议和网络配置相结合,可以实现更智能、更高效的网络流量管理策略。 + +### 实现 + +总体框架流程如下: + +![image-20240827134228586](./image/router1.png) + +我们通过两层判断来实现路由优化,首先我们维护了一个路由表,在其中寻找到对应路由时,快速找到对应MAC地址与对应重定向接口,其次,我们可以使用eBPF提供的路由查找函数,当查找成功时,将其加入我们维护的路由表之中。从而分别实现快慢转发(但均比普通的协议栈流程快) +核心代码如下 + +```c + pitem = bpf_map_lookup_elem(&rtcache_map, &daddr); + // 首先精确查找转发表,如果找到就直接转发,不必再经历最长前缀匹配的慢速通配查找 + // 这个动作是可以offload到硬件中的。 + if (pitem) { + __ip_decrease_ttl(iph); + memcpy(eth->h_dest, pitem->eth_dest, ETH_ALEN); + memcpy(eth->h_source, pitem->eth_source, ETH_ALEN); + bpf_printk("%s----daddr : %d prot:%d",fast_info,daddr,pitem->ifindex); + //bpf_trace_printk(fast_info, sizeof(fast_info), pitem->ifindex); + action = bpf_redirect(pitem->ifindex, 0); + goto out; + } + + // 否则只能执行最长前缀匹配了 + ifib.family = AF_INET; + ifib.tos = iph->tos; + ifib.l4_protocol = iph->protocol; + ifib.tot_len = bpf_ntohs(iph->tot_len); + ifib.ipv4_src = iph->saddr; + ifib.ipv4_dst = iph->daddr; + ifib.ifindex = ctx->ingress_ifindex; + + // 调用eBPF封装的路由查找函数,虽然所谓慢速查找,也依然不会进入协议栈的。 + if (bpf_fib_lookup(ctx, &ifib, sizeof(ifib), 0) == 0) { + struct rt_item_tab nitem; + + __builtin_memset(&nitem, 0, sizeof(nitem)); + memcpy(&nitem.eth_dest, ifib.dmac, ETH_ALEN); + memcpy(&nitem.eth_source, ifib.smac, ETH_ALEN); + nitem.ifindex = ifib.ifindex; + // 插入新的表项 + bpf_map_update_elem(&rtcache_map, &daddr, &nitem, BPF_ANY); + __ip_decrease_ttl(iph); + memcpy(eth->h_dest, ifib.dmac, ETH_ALEN); + memcpy(eth->h_source, ifib.smac, ETH_ALEN); + bpf_printk("%s----daddr : %d prot:%d",slow_info,daddr,nitem.ifindex); + //bpf_trace_printk(slow_info, sizeof(slow_info), ifib.ifindex); + action = bpf_redirect(ifib.ifindex, 0); + goto out; + } +``` +### 环境搭建 + +为了模拟真实的网络环境,我们部署了一个包含两个主机和一个路由器的虚拟化环境,并涉及两个不同的网段。该环境通过虚拟机进行仿真,提供了一个可靠的测试平台,用于评估网络配置、路由优化及流量管理策略。 + +![image-20240827135423757](./image/router2.png) + +其中PC1的ip为192.168.1.2/24,默认网关为192.168.1.1; + +PC2的ip为192.168.2.2/24,默认网关为192.168.2.1 + +在PC3上,我们在其网卡上配置多个虚拟接口,并启用IP转发,使其充当路由器连接两个网段。分别将ip设置为192.168.1.1与192.168.2.1; + +![image-20240827142446699](./image/router3.png) + +## 使用方法 + +本功能的使用命令为 + +```c +sudo ./netmanager -d enp1s0 -S --progname=xdp_entry_router -m +``` + +之后我们在PC1上访问PC2,其可以正常进行连接,并且在PC3上有相应的输出,证明其是被路由优化了 + +![image-20240827140027065](./image/router4.png) + diff --git a/eBPF_Supermarket/Network_Subsystem/net_manager/document/sockmap.md b/eBPF_Supermarket/Network_Subsystem/net_manager/document/sockmap.md new file mode 100644 index 000000000..bd99c8d62 --- /dev/null +++ b/eBPF_Supermarket/Network_Subsystem/net_manager/document/sockmap.md @@ -0,0 +1,347 @@ +# 优化同主机内多个进程之间的网络包传输 + +### 简介 + +结合 XDP和 socketmap 技术,针对源和目的端均在同一台机器的应用场景,实现数据传输路径的高效优化。利用 XDP 的高性能数据处理能力,工具能够绕过传统的 TCP/IP 协议栈,将数据直接发送至 socket 对端。这样不仅减少了协议栈处理的开销,还显著降低了延迟,提升了整体系统的吞吐量,适用于本地高并发、高性能的通信场景。 + +对于**源和目的端都在同一台机器**的应用来说,可以通过这种方式 **绕过整个 TCP/IP 协议栈**,直接将数据发送到 socket 对端 + +![image-20240909155744399](./image/socketmap1.png) + +相关依赖: + +1. sockmap:这是一个存储 socket 信息的映射表。作用: + + 1. 一段 BPF 程序**监听所有的内核 socket 事件**,并将新建的 socket 记录到这个 map; + 2. 另一段 BPF 程序**拦截所有 `sendmsg` 系统调用**,然后去 map 里查找 socket 对端,之后 调用 BPF 函数绕过 TCP/IP 协议栈,直接将数据发送到对端的 socket queue。 + +2. cgroups:指定要**监听哪个范围内的 sockets 事件**,进而决定了稍后要对哪些 socket 做重定向。 + + sockmap 需要关联到某个 cgroup,然后这个 cgroup 内的所有 socket 就都会执行加 载的 BPF 程序。 + +> cgroup,用于将进程分组并对这些进程施加资源限制和管理。 +> +> 1. **资源限制**:可以限制进程组使用的资源数量,例如限制一个进程组只能使用特定数量的内存或CPU时间。 +> 2. **优先级分配**:可以设置不同进程组之间的优先级,以确保某些关键进程获得更多资源。 +> 3. **资源监控**:可以监控每个cgroup的资源使用情况,帮助管理员分析和优化资源分配。 +> 4. **进程隔离**:通过将进程分组,能够实现进程之间的隔离,避免不同进程相互影响。 +> 5. **进程冻结**:可以暂停某个cgroup中的所有进程,暂时停止该组的运行。 + +### 实现 + +#### BPF类型 + +能拦截到 socket 操作(例如 TCP `connect`、`sendmsg` 等)的类型: + +- `BPF_PROG_TYPE_SOCK_OPS`:socket operations 事件触发执行。 +- `BPF_PROG_TYPE_SK_MSG`:`sendmsg()` 系统调用触发执行。**** + +创建一个全局的**映射表**(map)来**记录所有的 socket 信息**。基于这个 sockmap,编写两段 BPF 程序分别完成以下功能: + +- 程序一:拦截所有 TCP connection 事件,然后将 socket 信息存储到这个 map; +- 程序二:拦截所有 `sendmsg()` 系统调用,然后从 map 中查 询这个socket 信息,之后直接将数据**重定向到对端**。 + +#### 存储socket信息 + +1. **系统中有 socket 操作时**(例如 connection establishment、tcp retransmit 等),触发执行; + - **指定加载位置来实现**:`__section("sockops")` +2. **执行逻辑**:提取 socket 信息,并以 key & value 形式存储到 sockmap。**** + +```c +SEC("sockops") // 加载到 ELF 中的 `sockops` 区域,有 socket operations 时触发执行 +int bpf_sockmap(struct bpf_sock_ops *skops) +{ + switch (skops->op) { + case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: // 被动建连 + case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: // 主动建连 + if (skops->family == 2) { // AF_INET + bpf_sock_ops_ipv4(skops); // 将 socket 信息记录到到 sockmap + } + break; + default: + break; + } + return 0; +} +``` + +对于**两端都在本节点**的 socket 来说,这段代码会执行两次: + +- **源端发送 SYN 时**会产生一个事件,命中 case 2 +- **目的端发送 SYN+ACK 时**会产生一个事件,命中 case 1 + +因此对于每一个成功建连的 socket,sockmap 中会有两条记录(key 不同)。 + +提取 socket 信息以存储到 sockmap 是由函数 `bpf_sock_ops_ipv4()` 完成的。 + +```c +static inline +void bpf_sock_ops_ipv4(struct bpf_sock_ops *skops) +{ + struct sock_key key = {}; + int ret; + + extract_key4_from_ops(skops, &key); + ret = bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST); + //ret = sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST); + if (ret != 0) { + bpf_printk("sock_hash_update() failed, ret: %d\n", ret); + } + + bpf_printk("sockmap: op %d, port %d --> %d\n", skops->op, skops->local_port, bpf_ntohl(skops->remote_port)); +} +``` + +1. 调用 `extract_key4_from_ops()` 从 `struct bpf_sock_ops *skops`(socket metadata)中提取 key; +2. 调用 `sock_hash_update()` 将 key:value 写入全局的 sockmap `sock_ops_map`,这 个变量定义在我们的头文件中。 + +##### 提取sockmap key + +map 的类型可以是: + +- `BPF_MAP_TYPE_SOCKMAP` +- `BPF_MAP_TYPE_SOCKHASH` + +```c +struct{ + __uint(type, BPF_MAP_TYPE_SOCKHASH); + __type(key,struct sock_key); + __type(value, int); + __uint(max_entries, 65535); +}sock_ops_map SEC(".maps"); +``` + +key 定义如下: + +```c +struct sock_key { + uint32_t sip4; // 源 IP + uint32_t dip4; // 目的 IP + uint8_t family; // 协议类型 + uint8_t pad1; // this padding required for 64bit alignment + uint16_t pad2; // else ebpf kernel verifier rejects loading of the program + uint32_t pad3; + uint32_t sport; // 源端口 + uint32_t dport; // 目的端口 +} __attribute__((packed)); +``` + +提取 key 的实现 + +```c +static inline +void extract_key4_from_ops(struct bpf_sock_ops *ops, struct sock_key *key) +{ + // keep ip and port in network byte order + key->dip4 = ops->remote_ip4; + key->sip4 = ops->local_ip4; + key->family = 1; + + // local_port is in host byte order, and remote_port is in network byte order + key->sport = (bpf_htonl(ops->local_port) >> 16); + key->dport = FORCE_READ(ops->remote_port) >> 16; +} +``` + +##### 插入 sockmap + +`sock_hash_update()` 将 socket 信息写入到 sockmap,这个函数是我们定义的一个宏, 会展开成内核提供的一个 hash update 函数 + +#### 拦截 `sendmsg` 系统调用,socket 重定向 + +1. 拦截所有的 `sendmsg` 系统调用,从消息中提取 key; + + 在 socket 发起 `sendmsg` 系统调用时**触发执行**, + + - **指定加载位置来实现**:`__section("sk_msg")` + +2. 根据 key 查询 sockmap,找到这个 socket 的对端,然后绕过 TCP/IP 协议栈,直接将 数据重定向过去。 + +​ 通过将 sockmap attach 到 BPF 程序实现:map 中的所有 socket 都会继承这段程序, 因此其中的任何 socket 触发 sendmsg 系统调用时,都会执行到这段代码。 + +##### 从 socket message 中提取 key + +```c +SEC("sk_msg") // 加载目标文件(ELF )中的 `sk_msg` section,`sendmsg` 系统调用时触发执行 +int bpf_redir(struct sk_msg_md *msg) +{ + struct sock_key key = {}; + extract_key4_from_msg(msg, &key); + bpf_msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS); + bpf_printk("bpf_msg_redirect_hash successful!"); + return SK_PASS; +} +``` + +##### Socket 重定向 + +`msg_redirect_hash()` 也是我们定义的一个宏,最终调用的是 BPF 内置的辅助函数。 + +> 最终需要用的其实是内核辅助函数 `bpf_msg_redirect_hash()`,但后者无法直接访问, 只能通过预定义的 `BPF_FUNC_msg_redirect_hash` 来访问,否则校验器无法通过。 + +`msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS)` 几个参数: + +- `struct sk_msg_md *msg`:用户可访问的待发送数据的元信息(metadata) +- `&sock_ops_map`:这个 BPF 程序 attach 到的 sockhash map +- `key`:在 map 中索引用的 key +- `BPF_F_INGRESS`:放到对端的哪个 queue(rx 还是 tx) + +### 使用方法 + +使用命令激活程序 + +```c +sudo ./netmanager -f +``` + +之后令开终端,并将该shell的pid记录在命名空间中 + +```shell +sudo bash -c "echo $$ >> /sys/fs/cgroup/foo/cgroup.procs" +``` + +之后任何在当前 shell 内启动的程序都将属于这个 cgroupv2 了 + +在这个shell中运行测试代码/测试用例,如访问本机的80端口 + +```shell +curl 127.0.0.1:80 +``` + +可以看到相应程序被触发 + +![image-20240911141019191](./image/socketmap2.png) + +编写测试用例,查看开启功能和不开启的耗时差距,获取从输入命令到相关端口收到信息的延时 + +```c +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT 8080 +#define BUFFER_SIZE 1024 +#define MESSAGE "Hello, this is a test!" + +int main() { + int server_fd, client_fd, new_socket; + struct sockaddr_in address; + int addrlen = sizeof(address); + char buffer[BUFFER_SIZE] = {0}; + struct timeval send_time, recv_time; + + pid_t pid = fork(); // 创建子进程 + + if (pid == 0) { + // 子进程:服务端代码 + printf("Starting server...\n"); + + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { + perror("Socket failed"); + exit(EXIT_FAILURE); + } + + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(PORT); + + // 绑定套接字 + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { + perror("Bind failed"); + close(server_fd); + exit(EXIT_FAILURE); + } + + // 开始监听 + if (listen(server_fd, 3) < 0) { + perror("Listen failed"); + close(server_fd); + exit(EXIT_FAILURE); + } + + printf("Server is listening on port %d...\n", PORT); + + // 接受客户端连接 + if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) { + perror("Accept failed"); + close(server_fd); + exit(EXIT_FAILURE); + } + + // 接收客户端消息 + read(new_socket, buffer, BUFFER_SIZE); + printf("Server received: %s\n", buffer); + + // 发送响应 + send(new_socket, "Message received", strlen("Message received"), 0); + + close(new_socket); + close(server_fd); + exit(0); // 服务端子进程结束 + } else { + // 父进程:客户端代码 + sleep(1); // 确保服务端先启动 + + printf("Starting client...\n"); + + struct sockaddr_in serv_addr; + + if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + printf("Socket creation error\n"); + return -1; + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PORT); + + // 使用本地回环地址 + if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { + printf("Invalid address / Address not supported\n"); + return -1; + } + + // 连接服务端 + if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { + printf("Connection Failed\n"); + return -1; + } + + // 记录发送时间 + gettimeofday(&send_time, NULL); + + // 发送消息 + send(client_fd, MESSAGE, strlen(MESSAGE), 0); + printf("Client sent: %s\n", MESSAGE); + + // 接收响应 + read(client_fd, buffer, BUFFER_SIZE); + + // 记录接收时间 + gettimeofday(&recv_time, NULL); + + // 计算延时 + long elapsed = (recv_time.tv_sec - send_time.tv_sec) * 1000000 + (recv_time.tv_usec - send_time.tv_usec); + printf("Round trip delay: %ld microseconds\n", elapsed); + + close(client_fd); + } + + return 0; +} + +``` + +启用前 + +![image-20240911142031072](./image/socketmap3.png) + +启用后 + +![image-20240911142047092](./image/socketmap4.png) + +可以看到使用功能之后可以减少本机网络通信的时间 \ No newline at end of file