Repository: kmesh-net/kmesh

Issue:
[OSPP 2025] Kmesh website automation capacity building and documentation optimization
After the Kmesh website was refactored, a feature for archiving old documents was added. Additionally, the kmeshctl documentation will be updated in sync with changes made to the kmeshctl component in the main Kmesh repository. Therefore, it is hoped that two automation tools can be added:

- Automatically synchronize the kmeshctl documentation from the main repository to the website repository.
- Enable automation during version releases to perform the following tasks when a tag is created: archive old documents, update version-related information in the documentation, and publish the new documentation.

Beyond this, we also hope to optimize the Chinese documentation. While the English documentation is maintained by a GitHub Action, the Chinese documentation lacks similar GitHub Action support. Ideally, a GitHub Action could be developed to check for typos and grammatical errors in the Chinese documentation. If this is not feasible, we still hope to conduct a review of the Chinese documentation.

Hints from issue comments:
@LiZhenCheng9527: ## OUTPUT

1. kmeshctl auto-sync tool
2. Automated document archiving and publishing tools
3. BONUS: Chinese Document Checking and Caretaker Tool Development@LiZhenCheng9527: /assign @yashisrani @kmesh-bot: @LiZhenCheng9527: GitHub didn't allow me to assign the following users: yashisrani.

Note that only [kmesh-net members](https://github.com/orgs/kmesh-net/people) with read permissions, repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time.
For more information please see [the contributor guide](https://git.k8s.io/community/contributors/guide/first-contribution.md#issue-assignment-in-github)

<details>

In response to [this](https://github.com/kmesh-net/kmesh/issues/1412#issuecomment-3031064381):

>/assign @yashisrani 


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

Solution patch:
diff --git a/.gemini/config.yaml b/.gemini/config.yaml
new file mode 100644
index 00000000..829d28b8
--- /dev/null
+++ b/.gemini/config.yaml
@@ -0,0 +1,10 @@
+have_fun: true
+code_review:
+  disable: false
+  comment_severity_threshold: HIGH
+  max_review_comments: -1
+  pull_request_opened:
+    help: false
+    summary: true
+    code_review: true
+ignore_patterns: []
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index c312fcbf..55e6e051 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ tools/vagrant/.vagrant/
 .run/
 # Visual Studio Code
 .vscode/
+.local/
 # Bazel
 /bazel-*
 # vi swap files
@@ -40,6 +41,7 @@ coverage.out
 # build artifacts
 *.so
 *.o
+*.o.tmp
 
 kernel/**/*.ko
 kernel/**/*.cmd
diff --git a/config/.gitignore b/config/.gitignore
new file mode 100644
index 00000000..8ba357ea
--- /dev/null
+++ b/config/.gitignore
@@ -0,0 +1 @@
+linux-bpf.h
\ No newline at end of file
diff --git a/docs/ebpf_unit_test_zh.md b/docs/ebpf_unit_test_zh.md
new file mode 100644
index 00000000..4388950b
--- /dev/null
+++ b/docs/ebpf_unit_test_zh.md
@@ -0,0 +1,242 @@
+# Kmesh eBPF 单元测试框架文档
+
+# 1. 框架概述
+
+Kmesh eBPF 单元测试框架是一个用于测试 eBPF 内核态程序的工具，支持多种 eBPF 程序类型的单元测试。该框架基于 Go 语言的单元测试框架，能够独立运行单个 eBPF 程序的测试，而无需加载整个 Kmesh 系统，从而提高测试效率和覆盖率。
+
+# 2. 目录结构
+
+测试框架的目录结构如下：
+
+```plaintext
+test/bpf_ut/
+├── bpftest/              # Go 语言实现的单元测试框架
+│   ├── bpf_test.go       # 测试框架核心逻辑以及辅助函数
+│   ├── trf.pb.go         # 由 trf.proto 生成的 Go 代码
+│   ├── trf.proto         # 测试结果格式定义
+│   ├── general_test.go   # bpf/kmesh/general相关测试用例以及辅助函数
+│   └── workload_test.go  # bpf/kmesh/workload相关测试用例以及辅助函数
+├── include/              # 测试相关头文件
+│   ├── ut_common.h       # 通用测试宏和函数
+│   ├── xdp_common.h      # XDP 测试相关函数和宏定义
+│   └── tc_common.h       # TC 测试相关函数和宏定义
+├── Makefile              # 构建和运行测试的脚本
+└── *_test.c              # 测试文件，如 xdp_authz_offload_test.c
+```
+
+# 3. 核心组件
+
+## 3.1 Go 语言实现的单元测试框架 (`bpftest/`)
+
+Go 语言实现的单元测试框架负责加载和执行 eBPF 程序，捕获测试结果并生成报告：
+
+- `bpf_test.go`：框架的核心组件，包含两种主要测试类型：
+  - **`unitTest_BPF_PROG_TEST_RUN`**：基于 `BPF_PROG_TEST_RUN` 机制，适用于 [BPF_PROG_TEST_RUN 文档](https://docs.ebpf.io/linux/syscall/BPF_PROG_TEST_RUN/)列出的支持测试 eBPF 程序类型。
+  - **`unitTest_BUILD_CONTEXT`**：针对 `BPF_PROG_TEST_RUN` 不支持的程序类型，需要在 Go 代码中构造自定义上下文，通过 `cilium/ebpf` 加载、执行并验证 eBPF 程序的行为。
+  - **`unitTests_*`**：针对每一个 `*_test.c` 测试文件，维护多个具体的测试项。
+- `trf.proto`：定义测试结果和测试过程日志格式的 Protocol Buffers 文件。
+
+## 3.2 测试头文件 (`include/`)
+
+- `ut_common.h`：提供测试宏和辅助函数，支持测试初始化、断言、日志记录等功能。
+- `xdp_common.h`：提供 XDP 程序测试相关的辅助函数，如构建和验证 XDP 数据包。
+- `tc_common.h`：提供 TC 程序测试相关的辅助函数，如构建和验证 TC 数据包。
+
+## 3.3 测试文件 (`*_test.c`)
+
+- 测试文件的核心在于引入单元测试相关的头文件（如 `ut_common.h`、`xdp_common.h` 和 `tc_common.h`）；mock 需要 mock 的下游函数；直接 include 需要测试的 eBPF 程序；编写内核态的测试逻辑。
+- 对于 `unittest_BPF_PROG_TEST_RUN` 类型的测试，一个测试项通常包含以下部分：
+  - mock：mock 需要 mock 的下游函数。
+  - include：直接 include 需要测试的 eBPF 程序。
+  - tail_call：使用 `tail_call` 机制调用被测试的 eBPF 程序。
+  - PKTGEN：生成测试数据包的函数，使用 `PKTGEN` 宏定义。
+  - JUMP：调用被测试的 eBPF 程序的函数，使用 `JUMP` 宏定义。
+  - CHECK：验证测试结果的函数，使用 `CHECK` 宏定义。
+- 对于 `unitTest_BUILD_CONTEXT` 类型的测试，测试项通常包含以下部分：
+  - mock：mock 需要 mock 的下游函数。
+  - include：直接 include 需要测试的 eBPF 程序。
+
+## 3.4 Makefile
+
+`Makefile` 负责使用 clang 编译 `*_test.c` 文件并生成相应的 `*_test.o` 文件，随后在执行时通过 Go 测试框架加载这些对象文件来进行测试。
+
+# 4. 测试框架工作原理
+
+## 4.1 `unitTest_BPF_PROG_TEST_RUN`
+
+该测试类型基于内核提供的 `BPF_PROG_TEST_RUN` 机制，用于直接验证支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序。通过内核态快速调用，可方便地测试针对 XDP、TC 等常见程序类型的逻辑。
+对应的用户态 Go 测试代码会加载生成的 `.o` 文件，遍历被测程序并执行测试。对于每个测试项，还可在 `setupInUserSpace` 函数中做额外的初始化（如设置全局配置或更新 eBPF Map）。
+
+## 4.2 `unitTest_BUILD_CONTEXT`
+
+当需要测试不支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序类型时，可使用 `unitTest_BUILD_CONTEXT`。这种测试模式需要在用户态自行构造上下文并载入 eBPF 对象文件，完成特殊场景下的验证。
+对应的用户态 Go 测试代码在 `workFunc` 中执行实际测试逻辑，如挂载 cgroup、加载并附加 sockops 程序，然后通过各种方式（如向 TCP 服务器发起连接）触发 eBPF 程序运行并进行验证。
+
+## 4.3 用户态 Go 测试代码
+
+无论采用哪种测试类型，都会在用户态利用 Go 测试框架对编译得到的 eBPF 程序进行加载、执行与结果校验。`bpf_test.go` 中定义了各类工具函数，如：
+
+- `loadAndRunSpec`：载入并初始化 `.o` 文件中的程序、Map。
+- `startLogReader`：读取 eBPF Map 中的 ringbuf 或日志输出。
+- `registerTailCall`：为特定测试场景注册 tail call。
+
+这样可以结合内核测试程序与用户态测试逻辑，为 eBPF 程序提供更完善的验证能力。
+
+# 5. 编写测试
+
+## 5.1 `unitTest_BPF_PROG_TEST_RUN`
+
+对于使用 `BPF_PROG_TEST_RUN` 机制的 eBPF 程序，测试文件通常包含 mock、include、tail_call、PKTGEN、JUMP、CHECK 等部分。
+
+eBPF 测试文件通常遵循以下结构：
+
+```c
+// sample_test.c
+#include "ut_common.h"
+#include "xdp_common.h"
+
+// 1. 定义必要的 eBPF 映射和常量
+
+// 2. 实现 PKTGEN 函数（生成测试数据包）
+PKTGEN("program_type", "test_name")
+int test_pktgen(struct xdp_md *ctx)
+{
+  // 设置测试数据
+  return build_xdp_packet(...);
+}
+
+// 3. 实现 JUMP 函数（调用被测试的 eBPF 程序）
+JUMP("program_type", "test_name")
+int test_jump(struct xdp_md *ctx)
+{
+  // 调用被测试的 eBPF 程序
+  bpf_tail_call(...);
+  return TEST_ERROR;
+}
+
+// 4. 实现 CHECK 函数（验证测试结果）
+CHECK("program_type", "test_name")
+int test_check(const struct xdp_md *ctx)
+{
+  // 验证测试结果
+  test_init();
+  check_xdp_packet(...);
+  test_finish();
+}
+```
+
+用户态 Go 代码需要在 `bpf_test.go` 中实现对应的测试逻辑。以下是一个简单的示例：
+
+```go
+func test(t *testing.T) {
+  tests := []unitTests_BPF_PROG_TEST_RUN{
+    {
+      objFilename: "sample_test.o",
+      uts: []unitTest_BPF_PROG_TEST_RUN{
+        {
+          name: "test_name", // 需要与 C 代码中的测试名称一致
+          setupInUserSpace: func(t *testing.T, coll *ebpf.Collection) {},
+        },
+      },
+    },
+  }
+
+  for _, tt := range tests {
+    t.Run(tt.objFilename, tt.run())
+  }
+}
+```
+
+## 5.2 `unitTest_BUILD_CONTEXT`
+
+当需要测试不支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序时，可使用 `unitTest_BUILD_CONTEXT`。在这种模式下，用户态测试会主动在 `workFunc` 中挂载 cgroup 并加载 eBPF 对象进行验证。例如：
+
+```c
+// workload_sockops_test.c
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_log.h"
+#include "bpf_common.h"
+
+// mock bpf_sk_storage_get
+struct sock_storage_data mock_storage = {
+  .via_waypoint = 1,
+};
+
+static void *mock_bpf_sk_storage_get(void *map, void *sk, void *value, __u64 flags)
+{
+  void *storage = NULL;
+  storage = bpf_sk_storage_get(map, sk, value, flags);
+  if (!storage && map == &map_of_sock_storage) {
+    storage = &mock_storage;
+  }
+  return storage;
+}
+
+#define bpf_sk_storage_get mock_bpf_sk_storage_get
+
+// 直接 include 需要测试的 eBPF 程序
+#include "workload/sockops.c"
+```
+
+对应的 Go 测试逻辑可能在 `workload_test.go` 里，通过附加到 cgroup 并建立 TCP 连接来触发 sockops：
+
+```go
+func TestWorkloadSockOps(t *testing.T) {
+  tests := []unitTests_BUILD_CONTEXT{
+    {
+      objFilename: "workload_sockops_test.o",
+      uts: []unitTest_BUILD_CONTEXT{
+        {
+          name: "sample_test",
+          workFunc: func(t *testing.T, cgroupPath, objFilePath string) {
+            // 加载 ebpf 内核态程序
+            coll, lk := load_bpf_2_cgroup(t, objFilePath, cgroupPath)
+            defer coll.Close()
+            defer lk.Close()
+
+            // 触发连接操作，检查 bpf_map 中记录结果
+          },
+        },
+      },
+    },
+  }
+  for _, tt := range tests {
+    t.Run(tt.objFilename, tt.run())
+  }
+}
+```
+
+## 5.3 测试宏
+
+框架提供了多种测试宏简化测试编写：
+
+- `test_log(fmt, ...)`：记录测试日志。
+- `assert(cond)`：断言条件为真，否则测试失败。
+- `test_fail() / test_fail_now()`：标记测试失败。
+- `test_skip() / test_skip_now()`：跳过当前测试。
+
+## 5.4 测试辅助宏
+
+对于 XDP 程序测试，框架提供了专门的辅助宏：
+
+- `build_xdp_packet`：构建 XDP 测试数据包。
+- `check_xdp_packet`：验证 XDP 数据包处理结果。
+
+对于 TC 程序测试，框架提供了专门的辅助宏：
+
+- `build_tc_packet`：构建 TC 测试数据包。
+- `check_tc_packet`：验证 TC 数据包处理结果。
+
+# 6. 运行测试
+
+测试可以通过 `Makefile` 中定义的命令运行：
+
+```bash
+cd kmesh
+make ebpf_unit_test
+```
+
+可以使用以下参数控制测试执行：
+
+- `V=1`：启用详细测试输出
diff --git a/docs/multiple dataplane_EN.md b/docs/multiple_dataplane_EN.md
similarity index 100%
rename from docs/multiple dataplane_EN.md
rename to docs/multiple_dataplane_EN.md
diff --git a/docs/pics/Chinese_doc_check.png b/docs/pics/Chinese_doc_check.png
new file mode 100644
index 00000000..44a87d13
Binary files /dev/null and b/docs/pics/Chinese_doc_check.png differ
diff --git a/docs/pics/kmeshctl-sync-1.png b/docs/pics/kmeshctl-sync-1.png
new file mode 100644
index 00000000..be061e74
Binary files /dev/null and b/docs/pics/kmeshctl-sync-1.png differ
diff --git a/docs/pics/kmeshctl-sync-2.png b/docs/pics/kmeshctl-sync-2.png
new file mode 100644
index 00000000..ff65e237
Binary files /dev/null and b/docs/pics/kmeshctl-sync-2.png differ
diff --git a/docs/pics/website-versioning-archiving-1.png b/docs/pics/website-versioning-archiving-1.png
new file mode 100644
index 00000000..4846aa20
Binary files /dev/null and b/docs/pics/website-versioning-archiving-1.png differ
diff --git a/docs/pics/website-versioning-archiving-2.png b/docs/pics/website-versioning-archiving-2.png
new file mode 100644
index 00000000..a1c716be
Binary files /dev/null and b/docs/pics/website-versioning-archiving-2.png differ
diff --git a/docs/proposal/Kmesh_website_automation.md b/docs/proposal/Kmesh_website_automation.md
new file mode 100644
index 00000000..8531c90f
--- /dev/null
+++ b/docs/proposal/Kmesh_website_automation.md
@@ -0,0 +1,428 @@
+---
+title: Proposal for Website automation and Chinese docs optimization
+authors:
+  - "@yashisrani"
+reviewers:
+  - "@lizhencheng"
+approvers:
+  - "@lizhencheng"
+
+creation-date: 2025-07-07
+---
+
+## Proposal for Website automation and Chinese docs optimization
+
+<!--
+This is the title of your KEP. Keep it short, simple, and descriptive. A good
+title can help communicate what the KEP is and should be considered as part of
+any review.
+-->
+
+Upstream issue: <https://github.com/kmesh-net/kmesh/issues/1412>
+
+### Summary
+
+<!--
+This section is incredibly important for producing high-quality, user-focused
+documentation such as release notes or a development roadmap.
+
+A good summary is probably at least a paragraph in length.
+-->
+
+- Currently, the Kmesh project lacks effective automation for its website and documentation. Documentation is manually copied to the website, leading to inconsistencies and duplicated effort. A recent website refactor added a feature to archive old documents, but the process still relies on manual CLI steps during each release.
+
+- The Chinese documentation lacks any automated checks for typos or grammatical errors.
+
+- The goal is to make docs updates effortless, ensure versioned archives and enhance Chinese content quality, all without manual steps.
+
+### Motivation
+
+<!--
+This section is for explicitly listing the motivation, goals, and non-goals of
+this KEP.  Describe why the change is important and the benefits to users.
+-->
+- Manual documentation updates and versioning in the Kmesh project are time-consuming and error-prone. For instance, each release requires developers to manually copy files, archive old documentation, and update the website, which diverts engineering resources from core development tasks. Similarly, the lack of automated checks for Chinese documentation risks publishing error-ridden content, reducing its usability.
+
+- Previous Kmesh workflows were manual and time-consuming, and new releases required repetitive manual steps, wasting engineering time.
+
+- Automating these tasks will free the team to focus on building new features rather than maintaining docs by hand.
+
+#### Goals
+
+<!--
+List the specific goals of the KEP. What is it trying to achieve? How will we
+know that this has succeeded?
+-->
+
+- Automate the synchronization of Kmeshctl documentation from the main repository to the website.
+- Automate Versioned documentation release process, including archiving old docs and publishing new versions.
+- (if feasible) Implement automated typo and grammar checks for chinese documentation.
+
+#### Non-Goals
+
+<!--
+What is out of scope for this KEP? Listing non-goals helps to focus discussion
+and make progress.
+-->
+
+- Optimizing English documentation (out of scope for this proposal).
+- Implementing selective file syncing for specific documentation subsets.
+
+### Proposal
+
+<!--
+This is where we get down to the specifics of what the proposal actually is.
+This should have enough detail that reviewers can understand exactly what
+you're proposing, but should not include things like API designs or
+implementation. What is the desired outcome and how do we measure success?.
+The "Design Details" section below is for the real
+nitty-gritty.
+-->
+
+#### 1. Kmeshctl Syncing Tool
+
+- **Solution - 1:**
+
+  - It clones `Kmesh-net/kmesh`, copies `docs/ctl/` to `website/docs/`, using Github action workflow.
+  - **Implementation:**
+
+    The Sync kmeshctl Docs workflow:
+
+    - Triggers on pushes to the main branch of kmesh-net/kmesh with changes in `docs/ctl/**`.
+    - Copies the `kmesh/docs/ctl/` folder to `website/docs/kmeshctl/`, creating the target folder if it doesn’t exist.
+    - Commits and pushes changes to `kmesh-net/website`.
+
+![design](/docs/pics/kmeshctl-sync-2.png)
+
+<br/>
+
+- **Workflow Explanation:**
+  - **Start**: A push to the main branch of kmesh-net/kmesh with changes in the `docs/ctl/`folder triggers the workflow.
+  - **Checkout Repositories**: The workflow downloads the `kmesh-net/kmesh` and `kmesh-net/website` repositories.
+  - **Validate**: Checks that the `kmesh/docs/ctl/` folder exists and contains files.
+  - **Create Folder**: Creates the `website/docs/kmeshctl/` folder if it doesn’t exist.
+  - **Sync Files**: Copies the kmeshctl docs from `kmesh/docs/ctl/` to `website/docs/kmeshctl/` using rsync.
+  - **Save Changes**: Commits and pushes the updated files to the `kmesh-net/website` repository.
+
+<br/>
+
+- **Pros:**
+  - **Keeps Docs Up-to-Date**: Automatically updates `website/docs/kmeshctl/` with the latest kmeshctl docs whenever changes are made to `kmesh-net/kmesh/docs/ctl/`, ensuring the website reflects current documentation.
+  - **Efficient Syncing**: Uses rsync to copy only changed files, making updates quick even for small changes.
+  - **Reliable**: Includes checks to ensure the `kmesh/docs/ctl/` folder and files exist, preventing errors if something’s missing.
+
+<br/>
+
+- **Cons:**
+  - **Limited to kmeshctl Docs**: Only syncs `kmesh/docs/ctl/` to `website/docs/kmeshctl/`. If you need to sync other folders (e.g., docs/guides/), the workflow would need modification (not an issue since you specified only kmeshctl).
+  - **Checkout Overhead**: Downloads both `kmesh-net/kmesh` and `kmesh-net/website` repositories, which may take ~5-10 seconds each, even with shallow clones. This is minor for small repos but could slow down if repos grow large.
+
+<br/>
+
+- **Solution - 2:**
+
+  - We can keep all those docs in one place (Website repo or main repo) and we can create script to copy all needed docs. this technique used by prometheus-operator.
+  - prometheus-operator having all documentation in main repository and it's website having shell script to copy all needed docs which they want to show on website.
+  - **Prometheus-Operaror** website shell-script :
+    `https://github.com/prometheus-operator/website/blob/main/synchronize.sh`
+
+![design](/docs/pics/kmeshctl-sync-1.png)
+
+**Workflow Explanation:**
+
+- A Netlify webhook is triggered by a commit merged to `kmesh-net/kmesh/main` with changes in `docs/`.
+- The shell script runs on the Netlify server, cloning both `kmesh-net/kmesh` and `kmesh-net/website` repositories.
+- It copies only the required folders (`application-layer`, `architecture`, `community`, `developer-guide`, `performance`, `setup`, `transport-layer`, `kmeshctl`) from `kmesh-net/kmesh/docs/` to `kmesh-net/website/docs/`.
+- The script triggers the Netlify build process to generate and deploy the updated website, without committing or pushing changes to the `kmesh-net/website` repository.
+  
+<br/>
+
+- **Pros:**
+  - Simplifies deployment on Netlify without requiring repository commits.
+  - Uses rsync (a fast tool inspired by Prometheus Operator) to copy only changed files, making updates quick even for small changes.
+  - Includes checks to ensure the `kmesh/docs/ctl/` folder and files exist, preventing errors if something’s missing.
+  
+<br/>  
+
+- **Cons:**
+  - limitations in scaling if the website repo grows significantly.
+  - Netlify-specific dependencies (e.g, Netlify CLI setup).
+
+---
+
+- **Advantages and Disadvantages of Each Solution:**
+
+  - **Solution-1 ( Advantages ):**
+
+    - Targets only the `docs/ctl/` directory, keeping updates isolated and reducing noise in the website repository.
+    - Includes validation steps to check for folder and file existence, minimizing the risk of errors.
+    - A straightforward and familiar approach using GitHub Actions.
+    - Keeps the main kmesh repository and website repository independent while syncing only the necessary files.
+  
+  - **Solution-1 ( Disadvantages ):**
+
+    - Limited to syncing only the `docs/ctl/` folder. Adding additional documentation would require workflow modifications.
+    - Creates commits in the website repository for every doc change, which may add commit noise.
+    - Tightly coupled to GitHub Actions, making it less portable if switching CI/CD platforms.
+    - Workflow can grow more complex when scaling to multiple documentation sections.
+
+  - **Solution-2 ( Advantages ):**
+
+    - Documentation remains in a single source of truth (main repository), simplifying management.
+    - Automatically triggered by Netlify webhook without the need to commit or push changes to the website repository.
+    - A proven approach used by Prometheus Operator, which makes it a reliable reference design.
+    - Uses rsync to efficiently copy only updated files.
+    - Capable of copying multiple folders in a single process, making it easier to scale for future documentation needs.
+
+  - **Solution-2 ( Disadvantages ):**
+
+    - If the website repository grows significantly, the script may face scaling limitations.
+    - Shell script complexity can increase as the documentation structure expands.
+    - Cloning repositories and copying files happen during the Netlify build, which may slightly increase build time.
+    - Tied to Netlify’s ecosystem and requires specific Netlify CLI setup.
+
+---
+
+#### 2. Versioning Workflow
+
+- **Solution - 1:**
+  - **Workflow Explanation:**
+    - When a new release is planned, a maintainer updates the `VERSION` file in `kmesh-net/website` (e.g., from `v1.0.0` to `v1.1.0`) and pushes the change.
+    - This triggers a CI pipeline in the `kmesh-net/website` repository.
+    - The pipeline checks out the website repository, sets up Node.js and Docusaurus, validates the `docs/` directory, creates a versioned snapshot (e.g., `versioned_docs/version-1.1.0/`) using the `VERSION` file content, and commits the changes back to the `kmesh-net/website` repository.
+    - This ensures historical docs are archived and accessible via Docusaurus’s version navigation.
+
+![design](/docs/pics/website-versioning-archiving-1.png)
+
+- **Pros:**
+  - CI pipeline automates versioning based on `VERSION` file changes, reducing manual effort.
+  - Reduces unnecessary CI runs.
+  - Simple version management.
+
+<br/>
+
+- **Cons:**
+  - Manual updates risk errors.
+  - Potential risks, if the `VERSION` file format is incorrect.
+  - No tag-based fallback.
+
+<br/>
+
+- **Solution - 2:**
+
+  - This workflow saves a snapshot of the kmesh website’s documentation (stored in `kmesh-net/website/docs/`) whenever a new version of the kmesh project is released (e.g., `v1.0.0`, `v2.0.0`). It uses Docusaurus, a tool that organizes documentation, to create a versioned archive (like a backup) of the docs, so users can view older versions of the website’s documentation, including the kmeshctl docs (added by the separate sync workflow).
+  - This ensures the website always shows the latest docs while keeping a history of past versions.
+  - **When It Runs:** The workflow starts when a new version tag (e.g., `v1.0.0`) is pushed to the kmesh-net/kmesh repository.
+
+![design](/docs/pics/website-versioning-archiving-2.png)
+
+**Workflow Explanation:**
+
+- **Clone Website Code:** Gets the `kmesh-net/website` repository, which contains the website’s documentation and Docusaurus setup.
+- **Set Up Docusaurus:** Install Docusaurus, the tool used to manage and version the website’s docs.
+- **Check Docs Folder:** Makes sure the `website/docs/` folder exists and has files to avoid errors.
+- **Get Version Number:** Extracts the version tag (e.g., `v1.0.0`) to use for archiving.
+- **Archive Docs:** Saves a snapshot of the `website/docs/` folder (including kmeshctl/) as a versioned copy (e.g., `v1.0.0`) using Docusaurus.
+- **Save Changes:** Updates the website repository with the versioned snapshot and Docusaurus settings.
+
+<br/>
+
+- **Pros:**
+  - **Saves Version History:** Archives all website docs (including kmeshctl/) for each release (e.g., v1.0.0), enabling users to access documentation for specific kmesh versions via Docusaurus’s version navigation.
+  - **Fast Execution:** Completes in ~15-20 seconds using shallow clones and cached Docusaurus dependencies, efficient for infrequent tag pushes.
+  - **Reliable:** Checks that website/docs/ exists and contains files, preventing failures due to missing or empty folders.
+
+<br/>
+
+- **Cons:**
+  - **Cross-Repository Trigger:** Runs in kmesh-net/kmesh (triggered by tags) but modifies kmesh-net/website, which may confuse contributors expecting the workflow in the website repo.
+  - **Token Dependency:** Requires `WEBSITE_REPO_TOKEN` with write access to kmesh-net/website, needing secure setup and documentation.
+  - **Docusaurus Dependency:** Assumes kmesh-net/website is a properly configured Docusaurus project. Misconfiguration could cause failures, requiring testing to confirm setup.
+
+---
+
+#### 3. Chinese Docs Workflow (BONUS)
+
+- **Solution - 1:**
+
+  - Checks `kmesh/website/docs/` for typos/grammar using **LanguageTool (Open Source)** or **Tencent/Alibaba (Paid APIs)**, show Logs errors for manual reviews.
+  
+![design](/docs/pics/Chinese_doc_check.png)
+
+```yaml
+name: Chinese Grammar Check
+
+on:
+  push:
+    branches: [main]
+    paths:
+      - 'docs/**'
+      - 'docs/proposal/**'
+  pull_request:
+    branches: [main]
+    paths:
+      - 'docs/**'
+      - 'docs/proposal/**'
+
+jobs:
+  grammar-check:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.10'
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install pycorrector
+
+      - name: Run grammar check
+        run: |
+          python -c "
+          import os
+          import pycorrector
+          from pathlib import Path
+
+          def check_grammar_in_file(file_path):
+              with open(file_path, 'r', encoding='utf-8') as file:
+                  text = file.read()
+              errors = pycorrector.detect(text)
+              return errors
+
+          directories = ['docs', 'docs/proposal']
+          error_found = False
+          error_messages = []
+
+          for directory in directories:
+              for file_path in Path(directory).rglob('*.md'):
+                  if file_path.is_file():
+                      print(f'Checking file: {file_path}')
+                      errors = check_grammar_in_file(file_path)
+                      if errors:
+                          error_found = True
+                          error_messages.append(f'Grammar errors in {file_path}:')
+                          for error in errors:
+                              error_messages.append(f' - {error}')
+                      else:
+                          print(f'No grammar errors found in {file_path}')
+
+          if error_found:
+              print('\nGrammar errors found:')
+              for msg in error_messages:
+                  print(msg)
+              exit(1)
+          else:
+              print('\nNo grammar errors found in any files.')
+              exit(0)
+          "
+```
+
+---
+
+#### Preferred Solution
+
+- After evaluating both approaches for each identified problem, we recommend adopting Solution 1 for all three workflows (Kmeshctl Syncing Tool, Versioning Workflow, and Chinese Docs Workflow).
+  - **For Kmeshctl syncing,** Solution 1 provides a lightweight and reliable GitHub Actions-based mechanism to automatically keep `website/docs/kmeshctl/` in sync with changes to `docs/ctl/` in the main repository. This approach minimizes manual intervention and ensures the website always reflects the latest kmeshctl documentation.
+  - **For versioning,** Solution 1 simplifies version management by leveraging a `VERSION` file and a dedicated CI pipeline within the website repository. This design is straightforward to maintain, avoids cross-repository complexity, and provides a controlled mechanism for creating and archiving versioned documentation snapshots.
+  - **For Chinese documentation checks,** Solution offers an automated grammar and typo validation using open-source tools. This solution seamlessly integrates into the CI pipeline, providing immediate feedback on errors without requiring paid external services or complex setups.
+  
+---
+
+#### User Stories (Optional)
+
+<!--
+Detail the things that people will be able to do if this KEP is implemented.
+Include as much detail as possible so that people can understand the "how" of
+the system. The goal here is to make this feel real for users without getting
+bogged down.
+-->
+
+##### Story 1
+
+- As a Kmesh developer, I want `kmesh/docs/` changes to automatically trigger a Netlify build and deploy the updated website, so users see the latest docs without manual intervention.
+
+##### Story 2
+
+- As a website maintainer, I want versioning to happen automatically when the VERSION file is updated, archiving old docs and committing changes via CI.
+
+#### Notes/Constraints/Caveats (Optional)
+
+<!--
+What are the caveats to the proposal?
+What are some important details that didn't come across above?
+Go in to as much detail as necessary here.
+This might be a good place to talk about core concepts and how they relate.
+-->
+
+#### Risks and Mitigations
+
+<!--
+What are the risks of this proposal, and how do we mitigate?
+
+How will security be reviewed, and by whom?
+
+How will UX be reviewed, and by whom?
+
+Consider including folks who also work outside the SIG or subproject.
+-->
+
+- **Risk**: versioning fails if Docusaurus is misconfigured.
+  - **Mitigation**: Test Docusaurus setup locally before deployment.
+
+- **Risk**: LanguageTool misses Chinese-specific errors.
+  - **Mitigation**: Use Slack notifications for manual review; test LanguageTool locally.
+
+### Design Details
+
+<!--
+This section should contain enough information that the specifics of your
+change are understandable. This may include API specs (though not always
+required) or even code snippets. If there's any ambiguity about HOW your
+proposal will be implemented, this is the place to discuss them.
+-->
+
+#### Test Plan
+
+<!--
+**Note:** *Not required until targeted at a release.*
+
+Consider the following in developing a test plan for this enhancement:
+- Will there be e2e and integration tests, in addition to unit tests?
+- How will it be tested in isolation vs with other components?
+
+No need to outline all test cases, just the general strategy. Anything
+that would count as tricky in the implementation, and anything particularly
+challenging to test, should be called out.
+
+-->
+
+- **kmeshctl Sync Script:** run `./scripts/sync-kmeshctl-docs.sh` to verify file copying.
+- **Act Tool:** We can use `act` to test workflows locally.
+
+`act -w .github/workflows/kmeshctl-sync-docs.yml`
+`act -W .github/workflows/trigger-netlify-build.yml`
+`act -W .github/workflows/version-and-publish-docs.yml`
+`act -W .github/workflows/check-chinese-docs.yml`
+
+Requires Docker and act installed, set `WEBSITE_REPO_TOKEN` in `.env`
+
+- **Clone local repo:** We can clone the kmesh repo, push changes to that repo and test the workflow.
+
+### Alternatives
+
+<!--
+What other approaches did you consider, and why did you rule them out? These do
+not need to be as detailed as the proposal, but should include enough
+information to express the idea and why it was not acceptable.
+-->
+
+<!--
+Note: This is a simplified version of kubernetes enhancement proposal template.
+https://github.com/kubernetes/enhancements/tree/3317d4cb548c396a430d1c1ac6625226018adf6a/keps/NNNN-kep-template
+-->
diff --git a/go.mod b/go.mod
index e5b2cc5f..eb701a70 100644
--- a/go.mod
+++ b/go.mod
@@ -22,10 +22,10 @@ require (
 	github.com/safchain/ethtool v0.5.10
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/cobra v1.8.1
-	github.com/spf13/pflag v1.0.6
+	github.com/spf13/pflag v1.0.7
 	github.com/stretchr/testify v1.10.0
 	github.com/vishvananda/netlink v1.3.1
-	golang.org/x/sys v0.32.0
+	golang.org/x/sys v0.34.0
 	google.golang.org/grpc v1.70.0
 	google.golang.org/protobuf v1.36.5
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
@@ -207,7 +207,7 @@ require (
 	golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect
 	golang.org/x/mod v0.24.0 // indirect
 	golang.org/x/net v0.39.0 // indirect
-	golang.org/x/oauth2 v0.24.0 // indirect
+	golang.org/x/oauth2 v0.27.0 // indirect
 	golang.org/x/sync v0.13.0 // indirect
 	golang.org/x/term v0.31.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
@@ -223,7 +223,7 @@ require (
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	helm.sh/helm/v3 v3.16.3 // indirect
+	helm.sh/helm/v3 v3.16.4 // indirect
 	istio.io/client-go v1.24.2-0.20241206152608-3892aa679051 // indirect
 	k8s.io/apiextensions-apiserver v0.32.0 // indirect
 	k8s.io/apiserver v0.32.0 // indirect
diff --git a/go.sum b/go.sum
index 5d08cceb..98200721 100644
--- a/go.sum
+++ b/go.sum
@@ -404,8 +404,8 @@ github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA
 github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
 github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
-github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
+github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
 github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
 github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
@@ -532,8 +532,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
 golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
-golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
+golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -568,8 +568,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
+golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -663,8 +663,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
 gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
-helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY=
-helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU=
+helm.sh/helm/v3 v3.16.4 h1:rBn/h9MACw+QlhxQTjpl8Ifx+VTWaYsw3rguGBYBzr0=
+helm.sh/helm/v3 v3.16.4/go.mod h1:k8QPotUt57wWbi90w3LNmg3/MWcLPigVv+0/X4B8BzA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 istio.io/api v1.24.3 h1:iwWWPM0uEQ+oxRHvIWoB8MQ4bjF3dRQj+M5IDVczg0M=
diff --git a/hack/format.sh b/hack/format.sh
index d88f5a2b..b0739f5b 100755
--- a/hack/format.sh
+++ b/hack/format.sh
@@ -33,5 +33,10 @@ install_shell_format
 shfmt -w -s -ln=bash ./
 
 if [ -z "$GITHUB_ACTIONS" ]; then
-	docker run -u 0 -w /workspace -v "$PWD":/workspace davidanson/markdownlint-cli2:v0.18.1 "**/*.md" "--fix"
+	docker run --rm \
+		-v "$PWD":/workspace \
+		-w /workspace \
+		--user "$(id -u):$(id -g)" \
+		davidanson/markdownlint-cli2:v0.18.1 \
+		markdownlint-cli2 --fix "**/*.md"
 fi
diff --git a/kmesh_compile_env_pre.sh b/kmesh_compile_env_pre.sh
index 8723cd1e..fc28bc96 100644
--- a/kmesh_compile_env_pre.sh
+++ b/kmesh_compile_env_pre.sh
@@ -12,7 +12,6 @@ function install_libboundscheck() {
 }
 
 function dependency_pkg_install() {
-
 	if command -v apt >/dev/null; then
 		echo "Checking for required packages on a Debian-based system..."
 
diff --git a/pkg/controller/encryption/ipsec/ipsec_controller.go b/pkg/controller/encryption/ipsec/ipsec_controller.go
index c9640da9..d5c57563 100644
--- a/pkg/controller/encryption/ipsec/ipsec_controller.go
+++ b/pkg/controller/encryption/ipsec/ipsec_controller.go
@@ -290,12 +290,19 @@ func (c *IPSecController) handleKNIDelete(obj interface{}) {
 	deleteFunc := func(netns.NetNS) error {
 		for _, targetIP := range node.Spec.Addresses {
 			c.ipsecHandler.mutex.Lock()
-			_ = c.ipsecHandler.Clean(targetIP)
+			err := c.ipsecHandler.Clean(targetIP)
 			c.ipsecHandler.mutex.Unlock()
+			if err != nil {
+				return err
+			}
 		}
 		return nil
 	}
-	_ = netns.WithNetNSPath(nodeNsPath, deleteFunc)
+	err := netns.WithNetNSPath(nodeNsPath, deleteFunc)
+	if err != nil {
+		log.Errorf("failed to delete ipsec for node %s: %v", node.Name, err)
+		return
+	}
 	for _, podCIDR := range node.Spec.PodCIDRs {
 		c.deleteKNIMapCIDR(podCIDR, c.kniMap)
 	}
@@ -441,6 +448,7 @@ func (c *IPSecController) processNextItem() bool {
 			log.Errorf("failed to handle other node %s err: %v, giving up", name, err)
 			c.queue.Forget(key)
 		}
+		return true
 	}
 
 	c.queue.Forget(key)
diff --git a/pkg/status/status_server.go b/pkg/status/status_server.go
index 9486d4ce..bf6c52a4 100644
--- a/pkg/status/status_server.go
+++ b/pkg/status/status_server.go
@@ -23,6 +23,7 @@ import (
 	"io"
 	"net/http"
 	"net/http/pprof"
+	"sort"
 	"strconv"
 	"time"
 
@@ -568,6 +569,16 @@ func (s *Server) StopServer() error {
 }
 
 func printWorkloadDump(w http.ResponseWriter, wd WorkloadDump) {
+	sort.Slice(wd.Workloads, func(i, j int) bool {
+		return wd.Workloads[i].Name < wd.Workloads[j].Name
+	})
+	sort.Slice(wd.Services, func(i, j int) bool {
+		return wd.Services[i].Name < wd.Services[j].Name
+	})
+	sort.Slice(wd.Policies, func(i, j int) bool {
+		return wd.Policies[i].Name < wd.Policies[j].Name
+	})
+
 	data, err := json.MarshalIndent(wd, "", "    ")
 	if err != nil {
 		log.Errorf("Failed to marshal WorkloadDump: %v", err)



Test patch:
diff --git a/pkg/controller/encryption/ipsec/ipsec_controller_test.go b/pkg/controller/encryption/ipsec/ipsec_controller_test.go
new file mode 100644
index 00000000..c20df50b
--- /dev/null
+++ b/pkg/controller/encryption/ipsec/ipsec_controller_test.go
@@ -0,0 +1,751 @@
+/*
+ * Copyright The Kmesh Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ipsec
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/agiledragon/gomonkey/v2"
+	"github.com/cilium/ebpf"
+
+	netns "github.com/containernetworking/plugins/pkg/ns"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/vishvananda/netlink"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/client-go/kubernetes/fake"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/util/workqueue"
+
+	"kmesh.net/kmesh/daemon/options"
+	"kmesh.net/kmesh/pkg/bpf"
+	"kmesh.net/kmesh/pkg/constants"
+	"kmesh.net/kmesh/pkg/kube"
+	v1alpha1 "kmesh.net/kmesh/pkg/kube/apis/kmeshnodeinfo/v1alpha1"
+	fakeKmeshClientset "kmesh.net/kmesh/pkg/kube/nodeinfo/clientset/versioned/fake"
+	"kmesh.net/kmesh/pkg/utils"
+	"kmesh.net/kmesh/pkg/utils/test"
+)
+
+var (
+	testLocalNodeInfo = &v1alpha1.KmeshNodeInfo{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-local-node",
+			Namespace: "kmesh-system",
+		},
+		Spec: v1alpha1.KmeshNodeInfoSpec{
+			SPI:       123,
+			Addresses: []string{"10.0.0.1", "192.168.1.1"},
+			BootID:    "test-boot-id",
+			PodCIDRs:  []string{"10.244.0.1/24", "10.244.0.2/24"},
+		},
+	}
+
+	testRemoteNodeInfo = &v1alpha1.KmeshNodeInfo{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-remote-node",
+			Namespace: "kmesh-system",
+		},
+		Spec: v1alpha1.KmeshNodeInfoSpec{
+			SPI:       456,
+			Addresses: []string{"10.0.0.2"},
+			BootID:    "test-boot-id-2",
+			PodCIDRs:  []string{"10.244.1.1/24", "10.244.1.2/24"},
+		},
+	}
+
+	testK8sNode = &corev1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "test-local-node",
+		},
+		Status: corev1.NodeStatus{
+			Addresses: []corev1.NodeAddress{
+				{Type: corev1.NodeInternalIP, Address: "10.0.0.1"},
+				{Type: corev1.NodeExternalIP, Address: "192.168.1.1"},
+			},
+			NodeInfo: corev1.NodeSystemInfo{
+				BootID: "test-boot-id",
+			},
+		},
+		Spec: corev1.NodeSpec{
+			PodCIDRs: []string{"10.244.0.1/24", "10.244.0.2/24"},
+		},
+	}
+
+	testKey = IpSecKey{
+		Spi:         1,
+		AeadKeyName: "rfc4106(gcm(aes))",
+		AeadKey:     DecodeHex("abc9410d7cd6b324461bf16db518646594276c5362c30fc476ebca3f1a394b6ed4462161"),
+		Length:      128,
+	}
+)
+
+func prepareForController(t *testing.T) error {
+	// Create temporary directory and file for testing
+	tempDir := t.TempDir()
+	tempFile := filepath.Join(tempDir, "kmesh-ipsec", "ipSec")
+	err := os.MkdirAll(filepath.Dir(tempFile), 0755)
+	if err != nil {
+		return err
+	}
+
+	// Create a simple valid ipSec file content
+	keyJson, err := json.Marshal(testKey)
+	if err != nil {
+		return err
+	}
+
+	err = os.WriteFile(tempFile, keyJson, 0644) // create ./kmesh-ipsec/ipSec
+	if err != nil {
+		return err
+	}
+
+	// change workdir to tmpdir to read ipsec key file, to avoid none file error
+	oldDir, err := os.Getwd()
+	if err != nil {
+		return err
+	}
+	err = os.Chdir(tempDir)
+	if err != nil {
+		return err
+	}
+
+	old := os.Getenv("NODE_NAME")
+	os.Setenv("NODE_NAME", "test-local-node")
+	t.Cleanup(func() {
+		os.Chdir(oldDir)
+		os.Setenv("NODE_NAME", old)
+	})
+	return nil
+}
+
+func TestNewIPsecController(t *testing.T) {
+	testCases := []struct {
+		name          string
+		setupEnv      func() func()
+		expectedError bool
+		errorContains string
+	}{
+		{
+			name: "successful_creation",
+			setupEnv: func() func() {
+				return func() {
+					err := prepareForController(t)
+					assert.NoError(t, err)
+				}
+			},
+			expectedError: false,
+		},
+		{
+			name: "missing_node_name_env",
+			setupEnv: func() func() {
+				return func() {
+					os.Setenv("NODE_NAME", "")
+				}
+			},
+			expectedError: true,
+			errorContains: "failed to get kmesh node info from k8s",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			// Setup environment
+			setupEnv := test.setupEnv()
+			setupEnv()
+
+			// Setup patches
+			patches := gomonkey.NewPatches()
+			defer patches.Reset()
+
+			// Create fake clients
+			k8sClient := fake.NewSimpleClientset(testK8sNode)
+			kmeshClient := fakeKmeshClientset.NewSimpleClientset(testLocalNodeInfo)
+
+			// Create mock eBPF components
+			mockMap := &ebpf.Map{}
+			mockProg := &ebpf.Program{}
+
+			// Apply patches for kube.GetKmeshNodeInfoClient to return our fake client
+			clientPatches := gomonkey.NewPatches()
+			clientPatches.ApplyFuncReturn(kube.GetKmeshNodeInfoClient, kmeshClient, nil)
+			defer clientPatches.Reset()
+
+			controller, err := NewIPsecController(k8sClient, mockMap, mockProg)
+
+			if test.expectedError {
+				assert.Error(t, err)
+				if test.errorContains != "" {
+					assert.Contains(t, err.Error(), test.errorContains)
+				}
+				assert.Nil(t, controller)
+			} else {
+				assert.NoError(t, err)
+				assert.NotNil(t, controller)
+				assert.Equal(t, "test-local-node", controller.kmeshNodeInfo.Name)
+			}
+		})
+	}
+}
+
+func TestHandleKNIEvents(t *testing.T) {
+	err := prepareForController(t)
+	require.NoError(t, err)
+	// Setup patches
+	patches := gomonkey.NewPatches()
+	defer patches.Reset()
+
+	// Create fake clients
+	k8sClient := fake.NewSimpleClientset(testK8sNode)
+	kmeshClient := fakeKmeshClientset.NewSimpleClientset()
+	patches.ApplyFuncReturn(kube.GetKmeshNodeInfoClient, kmeshClient, nil)
+
+	prepare := func(t *testing.T) *IPSecController {
+		// Create mock eBPF components
+		mockMap := &ebpf.Map{}
+		mockProg := &ebpf.Program{}
+		controller, err := NewIPsecController(k8sClient, mockMap, mockProg)
+		assert.NoError(t, err)
+		assert.NotNil(t, controller)
+		return controller
+	}
+
+	t.Run("handleKNIAdd", func(t *testing.T) {
+		controller := prepare(t)
+
+		// Test adding a remote node (should be added to queue)
+		controller.handleKNIAdd(testRemoteNodeInfo)
+		assert.Eventually(t, func() bool { return controller.queue.Len() == 1 }, 100*time.Millisecond, 10*time.Millisecond)
+
+		// Test adding local node (should be ignored)
+		controller.queue = workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]()) // Reset queue
+		controller.handleKNIAdd(testLocalNodeInfo)
+		assert.Eventually(t, func() bool { return controller.queue.Len() == 0 }, 100*time.Millisecond, 10*time.Millisecond)
+	})
+
+	t.Run("handleKNIUpdate", func(t *testing.T) {
+		controller := prepare(t)
+
+		// Test updating with same spec (should be ignored)
+		controller.handleKNIUpdate(testRemoteNodeInfo, testRemoteNodeInfo)
+		assert.Eventually(t, func() bool { return controller.queue.Len() == 0 }, 100*time.Millisecond, 10*time.Millisecond)
+
+		// Test updating local node (should be ignored)
+		controller.handleKNIUpdate(testLocalNodeInfo, testLocalNodeInfo)
+		assert.Eventually(t, func() bool { return controller.queue.Len() == 0 }, 100*time.Millisecond, 10*time.Millisecond)
+
+		// Test updating with different spec (should be added to queue)
+		updatedNode := testRemoteNodeInfo.DeepCopy()
+		updatedNode.Spec.SPI = 789
+		controller.handleKNIUpdate(testRemoteNodeInfo, updatedNode)
+		assert.Eventually(t, func() bool { return controller.queue.Len() == 1 }, 100*time.Millisecond, 10*time.Millisecond)
+	})
+
+	t.Run("handleKNIDelete", func(t *testing.T) {
+		// Test case 1: Normal delete of remote node
+		t.Run("Normal delete of remote node", func(t *testing.T) {
+			controller := prepare(t)
+			// Create patches for network namespace operations
+			nsPatches := gomonkey.NewPatches()
+			defer nsPatches.Reset()
+
+			// Mock netns.WithNetNSPath to avoid actual network operations
+			nsPatches.ApplyFunc(netns.WithNetNSPath, func(nspath string, toRun func(netns.NetNS) error) error {
+				return toRun(nil) // Mock successful execution
+			})
+
+			// Mock ipsecHandler.Clean to track calls
+			cleanCallCount := 0
+			cleanPatches := gomonkey.NewPatches()
+			defer cleanPatches.Reset()
+			cleanPatches.ApplyMethod(controller.ipsecHandler, "Clean", func(_ *IpSecHandler, targetIP string) error {
+				cleanCallCount++
+				// Verify that the targetIP is one of the remote node's addresses
+				assert.Contains(t, testRemoteNodeInfo.Spec.Addresses, targetIP)
+				return nil
+			})
+
+			// Mock deleteKNIMapCIDR to track calls
+			deleteMapCallCount := 0
+			deleteMapPatches := gomonkey.NewPatches()
+			defer deleteMapPatches.Reset()
+			deleteMapPatches.ApplyPrivateMethod(reflect.TypeOf(controller), "deleteKNIMapCIDR", func(c *IPSecController, remoteCIDR string, mapfd *ebpf.Map) {
+				deleteMapCallCount++
+				// Verify that the remoteCIDR is one of the remote node's PodCIDRs
+				assert.Contains(t, testRemoteNodeInfo.Spec.PodCIDRs, remoteCIDR)
+			})
+
+			// Call handleKNIDelete with remote node
+			controller.handleKNIDelete(testRemoteNodeInfo)
+
+			// Verify that Clean was called for each address
+			assert.Equal(t, len(testRemoteNodeInfo.Spec.Addresses), cleanCallCount, "ipsecHandler.Clean should be called once for each address")
+			// Verify that deleteKNIMapCIDR was called for each PodCIDR
+			assert.Equal(t, len(testRemoteNodeInfo.Spec.PodCIDRs), deleteMapCallCount, "deleteKNIMapCIDR should be called once for each PodCIDR")
+		})
+
+		// Test case 2: WithNetNSPath error
+		t.Run("network_namespace_error", func(t *testing.T) {
+			controller := prepare(t)
+			// Create patches for network namespace operations that fail
+			nsPatches := gomonkey.NewPatches()
+			defer nsPatches.Reset()
+
+			nsPatches.ApplyFunc(netns.WithNetNSPath, func(nspath string, toRun func(netns.NetNS) error) error {
+				return fmt.Errorf("network namespace error")
+			})
+
+			// Mock deleteKNIMapCIDR to track calls
+			deleteMapCalled := false
+			deleteMapPatches := gomonkey.NewPatches()
+			defer deleteMapPatches.Reset()
+			deleteMapPatches.ApplyPrivateMethod(reflect.TypeOf(controller), "deleteKNIMapCIDR", func(c *IPSecController, remoteCIDR string, mapfd *ebpf.Map) {
+				deleteMapCalled = true
+			})
+
+			// Call handleKNIDelete - should not process map deletions when network operations fail
+			controller.handleKNIDelete(testRemoteNodeInfo)
+
+			// Verify that deleteKNIMapCIDR will not called when network namespace fail
+			assert.False(t, deleteMapCalled, "deleteKNIMapCIDR should not be called if network operations fail")
+		})
+	})
+}
+
+func getLoader(t *testing.T) (*bpf.BpfLoader, test.CleanupFn) {
+	config := options.BpfConfig{
+		Mode:        constants.DualEngineMode,
+		BpfFsPath:   "/sys/fs/bpf",
+		Cgroup2Path: "/mnt/kmesh_cgroup2",
+		EnableIPsec: true,
+	}
+	cleanfn, loader := test.InitBpfMap(t, config)
+
+	return loader, cleanfn
+}
+
+func TestMapOperations(t *testing.T) {
+	loader, cleanfn := getLoader(t)
+	t.Cleanup(cleanfn)
+	kniMap := loader.GetBpfWorkload().Tc.KmeshTcMarkEncryptObjects.KmNodeinfo
+	decryptProg := loader.GetBpfWorkload().Tc.KmeshTcMarkDecryptPrograms.TcMarkDecrypt
+
+	err := prepareForController(t)
+	require.NoError(t, err)
+	// Setup patches
+	patches := gomonkey.NewPatches()
+	defer patches.Reset()
+
+	// Create fake clients
+	k8sClient := fake.NewSimpleClientset(testK8sNode)
+	kmeshClient := fakeKmeshClientset.NewSimpleClientset()
+	patches.ApplyFuncReturn(kube.GetKmeshNodeInfoClient, kmeshClient, nil)
+
+	// Create controller
+	controller, err := NewIPsecController(k8sClient, kniMap, decryptProg)
+	require.NoError(t, err)
+	require.NotNil(t, controller)
+
+	// Test multiple address
+	testCases := []struct {
+		name     string
+		cidr     string
+		expected bool
+	}{
+		{"valid_ipv4_cidr", "192.168.1.0/24", true},
+		{"valid_ipv4_cidr_32", "10.0.0.1/32", true},
+		{"valid_ipv6_cidr", "2001:db8::/64", true},
+		{"invalid_cidr", "not-a-cidr", false},
+	}
+
+	for _, tc := range testCases { // not use t.Run to avoid parallel execution issues
+		// Add CIDR to KNI map
+		err := controller.updateKNIMapCIDR(tc.cidr, kniMap)
+		if tc.expected {
+			require.NoError(t, err)
+
+			// Verify add success
+			key, err := controller.generalKNIMapKey(tc.cidr)
+			require.NoError(t, err)
+			var value uint32
+			err = kniMap.Lookup(key, &value)
+			require.NoError(t, err)
+			assert.Equal(t, uint32(1), value)
+
+			// Delete CIDR
+			controller.deleteKNIMapCIDR(tc.cidr, kniMap)
+
+			// Verify delete success
+			err = kniMap.Lookup(key, &value)
+			assert.Error(t, err) // not found
+		} else {
+			assert.Error(t, err)
+		}
+	}
+}
+
+// Test update local kmesh node info and sync all node info
+func TestNodeOperations(t *testing.T) {
+	err := prepareForController(t)
+	require.NoError(t, err)
+
+	// Create clients
+	k8sClient := fake.NewSimpleClientset(testK8sNode)
+	kmeshClient := fakeKmeshClientset.NewSimpleClientset()
+
+	// Patch kube.GetKmeshNodeInfoClient to return our fake client
+	clientPatches := gomonkey.NewPatches()
+	clientPatches.ApplyFuncReturn(kube.GetKmeshNodeInfoClient, kmeshClient, nil)
+	defer clientPatches.Reset()
+
+	prepare := func(t *testing.T) (*IPSecController, chan struct{}) {
+		// Create mock eBPF components
+		mockMap := &ebpf.Map{}
+		mockProg := &ebpf.Program{}
+		stopCh := make(chan struct{})
+		controller, err := NewIPsecController(k8sClient, mockMap, mockProg)
+		assert.NoError(t, err)
+		assert.NotNil(t, controller)
+		go controller.informer.Run(stopCh)
+		if !cache.WaitForCacheSync(stopCh, controller.informer.HasSynced) {
+			t.Fatal("timed out waiting for caches to sync")
+		}
+		return controller, stopCh
+	}
+
+	t.Run("createAndUpdateLocalKmeshNodeInfo", func(t *testing.T) {
+		controller, stopCh := prepare(t)
+		defer close(stopCh)
+		// No node info
+		node, err := controller.lister.KmeshNodeInfos("kmesh-system").Get("test-local-node")
+		assert.Error(t, err)
+		assert.Nil(t, node)
+
+		err = controller.updateLocalKmeshNodeInfo()
+		assert.NoError(t, err)
+
+		// Wait for get node info
+		err = wait.PollUntilContextTimeout(context.Background(), 20*time.Millisecond, 200*time.Millisecond, false, func(context.Context) (bool, error) {
+			_, err := controller.lister.KmeshNodeInfos("kmesh-system").Get("test-local-node")
+			return err == nil, nil
+		})
+		assert.NoError(t, err)
+
+		node, err = controller.lister.KmeshNodeInfos("kmesh-system").Get("test-local-node")
+		assert.NoError(t, err)
+		assert.NotNil(t, node)
+		assert.Equal(t, "test-local-node", node.Name)
+		assert.Equal(t, 1, node.Spec.SPI) // should be same to testKey
+
+		// Test update local node info
+		// New node info
+		controller.kmeshNodeInfo.Spec.SPI = 2
+
+		err = controller.updateLocalKmeshNodeInfo()
+		assert.NoError(t, err)
+
+		// Wait for node info update
+		err = wait.PollUntilContextTimeout(context.Background(), 20*time.Millisecond, 200*time.Millisecond, false, func(context.Context) (bool, error) {
+			nodeinfo, err := controller.lister.KmeshNodeInfos("kmesh-system").Get("test-local-node")
+			if nodeinfo != nil && nodeinfo.Spec.SPI == 2 {
+				return true, nil
+			}
+			if err != nil {
+				return false, err
+			}
+			return false, nil
+		})
+		assert.NoError(t, err)
+
+		// lister can get new node info
+		node, err = controller.lister.KmeshNodeInfos("kmesh-system").Get("test-local-node")
+		assert.NoError(t, err)
+		assert.NotNil(t, node)
+		assert.Equal(t, 2, node.Spec.SPI) // new spi
+	})
+
+	t.Run("syncAllNodeInfo", func(t *testing.T) {
+		controller, stopCh := prepare(t)
+		defer close(stopCh)
+
+		// Create remote node
+		_, err = kmeshClient.KmeshV1alpha1().KmeshNodeInfos("kmesh-system").Create(context.TODO(), testRemoteNodeInfo, metav1.CreateOptions{})
+		assert.NoError(t, err)
+
+		// Patch handleOneNodeInfo
+		syncPatch := gomonkey.NewPatches()
+		syncPatch.ApplyPrivateMethod(reflect.TypeOf(controller), "handleOneNodeInfo", func(c *IPSecController, node *v1alpha1.KmeshNodeInfo) error {
+			if node.Name == testRemoteNodeInfo.Name { // test if get remote node info and ignore local node info
+				return nil
+			}
+			return fmt.Errorf("failed to get remote node info")
+		})
+		defer syncPatch.Reset()
+
+		go controller.informer.Run(stopCh)
+		if !cache.WaitForCacheSync(stopCh, controller.informer.HasSynced) {
+			t.Fatal("timed out waiting for caches to sync")
+		}
+
+		err = controller.syncAllNodeInfo()
+		assert.NoError(t, err)
+	})
+
+	// test handle testRemoteNodeInfo
+	t.Run("handleOneNodeInfo", func(t *testing.T) {
+		controller, stopCh := prepare(t)
+		defer close(stopCh)
+
+		patches := gomonkey.NewPatches()
+		defer patches.Reset()
+		podCIDRSet := make(map[string]bool)
+
+		for _, podCIDR := range testRemoteNodeInfo.Spec.PodCIDRs {
+			podCIDRSet[podCIDR] = true
+		}
+		patches.ApplyPrivateMethod(reflect.TypeOf(controller.ipsecHandler), "CreateXfrmRule", func(_ *IpSecHandler, localNode *v1alpha1.KmeshNodeInfo, remoteNode *v1alpha1.KmeshNodeInfo) error {
+			// mock, remote node info should be same with testRemoteNodeInfo
+			for _, podCIDR := range remoteNode.Spec.PodCIDRs {
+				if !podCIDRSet[podCIDR] {
+					return fmt.Errorf("remote node info podCIDRs is not equal")
+				}
+			}
+			return nil
+		})
+		patches.ApplyPrivateMethod(reflect.TypeOf(controller), "updateKNIMapCIDR", func(c *IPSecController, remoteCIDR string, mapfd *ebpf.Map) error {
+			// mock, remote node info should be same with testRemoteNodeInfo
+			if !podCIDRSet[remoteCIDR] {
+				return fmt.Errorf("remote node info podCIDRs is not equal")
+			}
+			return nil
+		})
+
+		patches.ApplyFunc(netns.WithNetNSPath, func(nspath string, toRun func(netns.NetNS) error) error {
+			return nil
+		})
+
+		err = controller.handleOneNodeInfo(testRemoteNodeInfo)
+		assert.NoError(t, err)
+	})
+}
+
+func TestProcessNextItem(t *testing.T) {
+	err := prepareForController(t)
+	require.NoError(t, err)
+
+	// Setup patches
+	patches := gomonkey.NewPatches()
+	defer patches.Reset()
+
+	// Create clients
+	k8sClient := fake.NewSimpleClientset(testK8sNode)
+	kmeshClient := fakeKmeshClientset.NewSimpleClientset()
+
+	// Apply patches for kube.GetKmeshNodeInfoClient to return our fake client
+	clientPatches := gomonkey.NewPatches()
+	clientPatches.ApplyFuncReturn(kube.GetKmeshNodeInfoClient, kmeshClient, nil)
+	defer clientPatches.Reset()
+
+	prepare := func(t *testing.T) (*IPSecController, chan struct{}) {
+		// Create mock eBPF components
+		mockMap := &ebpf.Map{}
+		mockProg := &ebpf.Program{}
+
+		controller, err := NewIPsecController(k8sClient, mockMap, mockProg)
+		assert.NoError(t, err)
+		assert.NotNil(t, controller)
+
+		stopCh := make(chan struct{})
+		go controller.informer.Run(stopCh)
+		if !cache.WaitForCacheSync(stopCh, controller.informer.HasSynced) {
+			t.Fatal("timed out waiting for caches to sync")
+		}
+		return controller, stopCh
+	}
+
+	t.Run("successful_processing", func(t *testing.T) {
+		controller, stopCh := prepare(t)
+		defer close(stopCh)
+
+		// Add remote node to queue
+		controller.queue.Add("test-remote-node")
+
+		// Mock handleOneNodeInfo to avoid complex IPsec operations
+		handlerPatches := gomonkey.NewPatches()
+		handlerPatches.ApplyPrivateMethod(reflect.TypeOf(controller), "handleOneNodeInfo", func(_ *IPSecController, _ *v1alpha1.KmeshNodeInfo) error {
+			return nil // Simulate success
+		})
+		defer handlerPatches.Reset()
+
+		// Process the item
+		result := controller.processNextItem()
+		assert.True(t, result)                     // Should return true indicating continue processing
+		assert.Equal(t, 0, controller.queue.Len()) // Item should be removed from queue
+	})
+
+	t.Run("non_existent_node", func(t *testing.T) {
+		controller, stopCh := prepare(t)
+		defer close(stopCh)
+
+		// Test with non-existent node
+		controller.queue.Add("non-existent-node")
+		result := controller.processNextItem()
+		controller.queue.Done("non-existent-node")
+		assert.True(t, result)                     // Should still return true
+		assert.Equal(t, 0, controller.queue.Len()) // Item should be removed from queue
+	})
+
+	// Test if the error returned by handleOneNodeInfo is not nil, the function should return true, and requeue the item or forget it based on the number of requeues
+	t.Run("handleOneNodeInfo_err", func(t *testing.T) {
+		controller, stopCh := prepare(t)
+		defer close(stopCh)
+
+		// Add local node to queue
+		controller.queue.Add("test-local-node")
+		failPatches := gomonkey.NewPatches()
+		failPatches.ApplyPrivateMethod(reflect.TypeOf(controller), "handleOneNodeInfo", func(_ *IPSecController, _ *v1alpha1.KmeshNodeInfo) error {
+			return fmt.Errorf("test error") // Simulate failure
+		})
+		defer failPatches.Reset()
+
+		// Create local node info
+		_, err = controller.knclient.Create(context.TODO(), &controller.kmeshNodeInfo, metav1.CreateOptions{})
+		assert.NoError(t, err)
+
+		// Wait
+		err = wait.PollUntilContextTimeout(context.Background(), 20*time.Millisecond, 200*time.Millisecond, false, func(context.Context) (bool, error) {
+			_, err := controller.lister.KmeshNodeInfos("kmesh-system").Get("test-local-node")
+			return err == nil, nil
+		})
+		assert.NoError(t, err)
+
+		for exceptRetryCount := 1; exceptRetryCount <= MaxRetries+1; exceptRetryCount++ {
+			result := controller.processNextItem()
+			assert.True(t, result) // Should still return true
+			// NumRequeues("test-local-node") will return the number of times the item has been requeued
+			// So, every time to call processNextItem, the number of requeues will increase by 1
+			// When the number of requeues is greater than MaxRetries, the item will be forgotten
+			assert.Equal(t, exceptRetryCount%(MaxRetries+1), controller.queue.NumRequeues("test-local-node"))
+		}
+	})
+}
+
+func TestHandleTc(t *testing.T) {
+	err := prepareForController(t)
+	require.NoError(t, err)
+
+	// Setup patches
+	patches := gomonkey.NewPatches()
+	defer patches.Reset()
+
+	// Create fake clients
+	k8sClient := fake.NewSimpleClientset(testK8sNode)
+	kmeshClient := fakeKmeshClientset.NewSimpleClientset()
+	patches.ApplyFuncReturn(kube.GetKmeshNodeInfoClient, kmeshClient, nil)
+
+	mockMap := &ebpf.Map{}
+	mockProg := &ebpf.Program{}
+
+	// Create controller
+	controller, err := NewIPsecController(k8sClient, mockMap, mockProg)
+	require.NoError(t, err)
+	require.NotNil(t, controller)
+
+	testCases := []struct {
+		name           string
+		mode           int
+		mockInterfaces []net.Interface
+		mockIfaceAddrs map[string][]net.Addr
+		mockLinkByName map[string]netlink.Link
+		expectedCalls  map[string]int // track expected calls to ManageTCProgram
+		expectedError  bool
+	}{
+		{
+			name: "successful_attach_tc_program",
+			mode: constants.TC_ATTACH,
+			mockInterfaces: []net.Interface{
+				{Index: 1, Name: "eth0", Flags: net.FlagUp},
+				{Index: 2, Name: "lo", Flags: net.FlagLoopback | net.FlagUp},
+				{Index: 3, Name: "eth1", Flags: net.FlagUp},
+			},
+			mockLinkByName: map[string]netlink.Link{
+				"eth0": &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: "eth0", Index: 1}},
+				"eth1": &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: "eth1", Index: 3}},
+			},
+			expectedError: false,
+		},
+		{
+			name: "successful_detach_tc_program",
+			mode: constants.TC_DETACH,
+			mockInterfaces: []net.Interface{
+				{Index: 1, Name: "eth0", Flags: net.FlagUp},
+			},
+			mockLinkByName: map[string]netlink.Link{
+				"eth0": &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: "eth0", Index: 1}},
+			},
+			expectedError: false,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			// mock Interfaces
+			interfacesPatches := gomonkey.NewPatches()
+			defer interfacesPatches.Reset()
+			interfacesPatches.ApplyFunc(net.Interfaces, func() ([]net.Interface, error) {
+				return test.mockInterfaces, nil
+			})
+			// mock IfaceContainIPs
+			ifaceContainIPsPatches := gomonkey.NewPatches()
+			defer ifaceContainIPsPatches.Reset()
+			ifaceContainIPsPatches.ApplyFunc(utils.IfaceContainIPs, func(iface net.Interface, IPs []string) (bool, error) {
+				return true, nil
+			})
+
+			// mock LinkByName
+			linkByNamePatches := gomonkey.NewPatches()
+			defer linkByNamePatches.Reset()
+			linkByNamePatches.ApplyFunc(netlink.LinkByName, func(name string) (netlink.Link, error) {
+				return test.mockLinkByName[name], nil
+			})
+			// mock ManageTCProgram
+			manageTCProgramPatches := gomonkey.NewPatches()
+			defer manageTCProgramPatches.Reset()
+			manageTCProgramPatches.ApplyFunc(utils.ManageTCProgram, func(link netlink.Link, tc *ebpf.Program, mode int) error {
+				return nil
+			})
+			err := controller.handleTc(test.mode)
+
+			// Verify results
+			if test.expectedError {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
+}
diff --git a/pkg/controller/encryption/ipsec/ipsec_handler_test.go b/pkg/controller/encryption/ipsec/ipsec_handler_test.go
new file mode 100644
index 00000000..4155e5f7
--- /dev/null
+++ b/pkg/controller/encryption/ipsec/ipsec_handler_test.go
@@ -0,0 +1,566 @@
+/*
+ * Copyright The Kmesh Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ipsec
+
+import (
+	"bytes"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"net"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/vishvananda/netlink"
+)
+
+// DecodeHex is a utility function to decode a hex string into bytes.
+func DecodeHex(s string) []byte {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+// TestLoadIPSecKey tests the loading of IPSec keys from different file sources
+func TestLoadIPSecKey(t *testing.T) {
+	aeadKey := DecodeHex("2dc9410d7cd6b324461bf16db518646594276c5362c30fc476ebca3f1a394b6ed4462161")
+	tests := []struct {
+		name        string
+		keyData     IpSecKey
+		expectError bool
+		errorMsg    string
+	}{
+		{ // Valid
+			name: "valid_rfc4106_key",
+			keyData: IpSecKey{
+				Spi:         1,
+				AeadKeyName: "rfc4106(gcm(aes))",
+				AeadKey:     aeadKey,
+				Length:      128,
+			},
+			expectError: false,
+		},
+		{
+			name: "invalid_algo_name",
+			keyData: IpSecKey{
+				Spi:         3,
+				AeadKeyName: "aes-gcm", // should start with "rfc"
+				AeadKey:     aeadKey,
+				Length:      128,
+			},
+			expectError: true,
+			errorMsg:    "invalid algo name, aead need begin with \"rfc\"",
+		},
+		{
+			name: "empty_algo_name",
+			keyData: IpSecKey{
+				Spi:         4,
+				AeadKeyName: "",
+				AeadKey:     aeadKey,
+				Length:      128,
+			},
+			expectError: true,
+			errorMsg:    "invalid algo name, aead need begin with \"rfc\"",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Create temporary test file
+			tmpDir := t.TempDir()
+			relativePath := strings.TrimPrefix(IpSecKeyFile, "./") // Remove leading "./" for temp file path
+			testFile := filepath.Join(tmpDir, relativePath)
+
+			os.MkdirAll(filepath.Dir(testFile), 0755) // Ensure directory exists
+
+			// Write test data to file
+			keyJSON, err := json.Marshal(tt.keyData)
+			require.NoError(t, err, "Failed to marshal test key data")
+
+			err = os.WriteFile(testFile, keyJSON, 0644)
+			require.NoError(t, err, "Failed to write test file")
+
+			// Create handler and test
+			handler := NewIpSecHandler()
+
+			// Test the function
+			err = handler.LoadIPSecKeyFromFile(testFile)
+
+			if tt.expectError {
+				assert.Error(t, err, "Expected error but got none")
+				if tt.errorMsg != "" {
+					assert.Contains(t, err.Error(), tt.errorMsg, "Error message doesn't match expected")
+				}
+			} else {
+				assert.NoError(t, err, "Unexpected error: %v", err)
+
+				// Verify the key was loaded correctly
+				assert.Equal(t, tt.keyData.Spi, handler.Spi, "SPI mismatch")
+
+				// Check if key is stored in history
+				storedKey, exists := handler.historyIpSecKey[tt.keyData.Spi]
+				assert.True(t, exists, "Key not found in history")
+				assert.Equal(t, tt.keyData.Spi, storedKey.Spi, "Stored SPI mismatch")
+				assert.Equal(t, tt.keyData.AeadKeyName, storedKey.AeadKeyName, "Stored AeadKeyName mismatch")
+				assert.Equal(t, tt.keyData.AeadKey, storedKey.AeadKey, "Stored AeadKey mismatch")
+				assert.Equal(t, tt.keyData.Length, storedKey.Length, "Stored Length mismatch")
+			}
+		})
+	}
+
+	// Test invalid JSON format
+	t.Run("invalid_json", func(t *testing.T) {
+		tmpDir := t.TempDir()
+		relatedPath := strings.TrimPrefix(IpSecKeyFile, "./") // Remove leading "./" for temp file path
+		testFile := filepath.Join(tmpDir, relatedPath)
+		os.MkdirAll(filepath.Dir(testFile), 0755) // Ensure directory exists
+
+		invalidJSON := []byte(`{ invalid json }`) // Invalid key data
+		err := os.WriteFile(testFile, invalidJSON, 0644)
+		require.NoError(t, err)
+
+		handler := NewIpSecHandler()
+		err = handler.LoadIPSecKeyFromFile(testFile)
+
+		assert.Error(t, err, "Expected error for invalid JSON")
+		assert.Contains(t, err.Error(), "ipsec config file decoder error", "Error should mention decoder error")
+	})
+
+	// Test file not found error
+	t.Run("file_not_found", func(t *testing.T) {
+		handler := NewIpSecHandler()
+
+		err := handler.LoadIPSecKeyFromFile("/non/existent/file")
+
+		assert.Error(t, err, "Expected error for non-existent file")
+		assert.Contains(t, err.Error(), "load ipsec keys failed", "Error message should indicate file loading failure")
+	})
+
+	// Test multiple key loading (should update history)
+	tests = []struct {
+		name        string
+		keyData     IpSecKey
+		expectError bool
+		errorMsg    string
+	}{
+		{
+			name: "first_key",
+			keyData: IpSecKey{
+				Spi:         1,
+				AeadKeyName: "rfc4106(gcm(aes))",
+				AeadKey:     aeadKey,
+				Length:      128,
+			},
+			expectError: false,
+		},
+		{
+			name: "second_key",
+			keyData: IpSecKey{
+				Spi:         2,
+				AeadKeyName: "rfc4106(gcm(aes))",
+				AeadKey:     DecodeHex("abc9410d7cd6b324461bf16db518646594276c5362c30fc476ebca3f1a394b6ed4462161"),
+				Length:      128,
+			},
+			expectError: false,
+		},
+	}
+	t.Run("multiple_key_loading", func(t *testing.T) {
+		handler := NewIpSecHandler()
+		tmpDir := t.TempDir()
+		relatedPath := strings.TrimPrefix(IpSecKeyFile, "./") // Remove leading "./" for temp file path
+		testFile := filepath.Join(tmpDir, relatedPath)
+		os.MkdirAll(filepath.Dir(testFile), 0755) // Ensure directory exists
+
+		for i, tt := range tests {
+			keyJSON, err := json.Marshal(tt.keyData)
+			require.NoError(t, err)
+			err = os.WriteFile(testFile, keyJSON, 0644)
+			require.NoError(t, err)
+
+			err = handler.LoadIPSecKeyFromFile(testFile)
+			assert.NoError(t, err)
+
+			assert.Equal(t, i+1, handler.Spi) // check spi
+			assert.Len(t, handler.historyIpSecKey, i+1)
+			_, exits := handler.historyIpSecKey[i+1] // check key exists
+			assert.True(t, exits, "key not exists")
+		}
+	})
+}
+
+// TestGenerateIPSecKey tests the IPSec key generation algorithm.
+func TestGenerateIPSecKey(t *testing.T) {
+	handler := NewIpSecHandler()
+	key := DecodeHex("2dc9410d7cd6b324461bf16db518646594276c5362c30fc476ebca3f1a394b6ed4462161")
+	tests := []struct {
+		name        string
+		srcIP       string
+		dstIP       string
+		srcBootID   string
+		dstBootID   string
+		key         []byte
+		description string
+	}{
+		{
+			name:        "same_input_same_output",
+			srcIP:       "10.0.0.1",
+			dstIP:       "10.0.0.2",
+			srcBootID:   "550e8400-e29b-41d4-a716-446655440000",
+			dstBootID:   "550e8400-e29b-41d4-a716-446655440001",
+			key:         key,
+			description: "Same inputs should produce same output",
+		},
+		{
+			name:        "different_src_different_output",
+			srcIP:       "10.0.0.3", // Different src IP
+			dstIP:       "10.0.0.2",
+			srcBootID:   "550e8400-e29b-41d4-a716-446655440000",
+			dstBootID:   "550e8400-e29b-41d4-a716-446655440001",
+			key:         key,
+			description: "Different src IP should produce different output",
+		},
+		{
+			name:        "different_bootid_different_output",
+			srcIP:       "10.0.0.1",
+			dstIP:       "10.0.0.2",
+			srcBootID:   "550e8400-e29b-41d4-a716-446655440002", // Different src Boot ID
+			dstBootID:   "550e8400-e29b-41d4-a716-446655440001",
+			key:         key,
+			description: "Different boot ID should produce different output",
+		},
+		{
+			name:        "different_key_different_output",
+			srcIP:       "10.0.0.1",
+			dstIP:       "10.0.0.2",
+			srcBootID:   "550e8400-e29b-41d4-a716-446655440000",
+			dstBootID:   "550e8400-e29b-41d4-a716-446655440001",
+			key:         DecodeHex("2dc9410d7cd6b324461bf16db518646594276c5362c30fc476ebca3f1a394b6ed4462abc"), // Different key
+			description: "Different key should produce different output",
+		},
+	}
+
+	// Store reference result for comparison
+	var referenceResult []byte
+
+	for i, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := handler.generateIPSecKey(tt.srcIP, tt.dstIP, tt.srcBootID, tt.dstBootID, tt.key)
+
+			// Basic validations
+			assert.NotNil(t, result, "Result should not be nil")
+			assert.Equal(t, len(tt.key), len(result), "Result length should match original key length")
+
+			// For deterministic test, same input should produce same output
+			if tt.name == "same_input_same_output" {
+				result2 := handler.generateIPSecKey(tt.srcIP, tt.dstIP, tt.srcBootID, tt.dstBootID, tt.key)
+				assert.Equal(t, result, result2, "Same inputs should produce identical results")
+			}
+
+			// Store reference for comparison
+			if i == 0 { // Use first test as reference
+				referenceResult = make([]byte, len(result))
+				copy(referenceResult, result)
+			}
+
+			// Compare with reference (expect same or different based on test case)
+			if i > 0 && referenceResult != nil {
+				assert.NotEqual(t, referenceResult, result, tt.description)
+			}
+		})
+	}
+}
+
+func hasStateRule(state *netlink.XfrmState) (bool, error) {
+	// Verify the state was added
+	states, err := netlink.XfrmStateList(netlink.FAMILY_ALL)
+	if err != nil {
+		return false, err
+	}
+
+	found := false
+	for _, s := range states {
+		if s.Src.Equal(state.Src) && s.Dst.Equal(state.Dst) && s.Spi == state.Spi &&
+			s.Proto == state.Proto && s.Mode == state.Mode &&
+			s.Aead.Name == state.Aead.Name && s.Aead.Key != nil && bytes.Equal(s.Aead.Key, state.Aead.Key) &&
+			s.Aead.ICVLen == state.Aead.ICVLen {
+			found = true
+		}
+	}
+	if !found {
+		return false, nil
+	}
+	return true, nil
+}
+
+// TestCreateStateRule tests XFRM state creation logic
+func TestCreateStateRule(t *testing.T) {
+	handler := NewIpSecHandler()
+	testKey := DecodeHex("2dc9410d7cd6b324461bf16db518646594276c5362c30fc476ebca3f1a394b6ed4462161")
+	ipsecKey := IpSecKey{
+		Spi:         1001,
+		AeadKeyName: "rfc4106(gcm(aes))",
+		AeadKey:     testKey,
+		Length:      128,
+	}
+
+	src := net.ParseIP("10.0.1.100")
+	dst := net.ParseIP("10.0.2.100")
+
+	state := &netlink.XfrmState{
+		Src:   src,
+		Dst:   dst,
+		Proto: netlink.XFRM_PROTO_ESP,
+		Mode:  netlink.XFRM_MODE_TUNNEL,
+		Spi:   ipsecKey.Spi,
+		Reqid: 1,
+		Aead: &netlink.XfrmStateAlgo{
+			Name:   ipsecKey.AeadKeyName,
+			Key:    testKey,
+			ICVLen: ipsecKey.Length,
+		},
+	}
+
+	t.Run("test_create_state_rule", func(t *testing.T) {
+		err := handler.createStateRule(state.Src, state.Dst, testKey, ipsecKey)
+
+		require.NoError(t, err, "Failed to add XFRM state rule: %v", err)
+		// Verify the state was added
+		found, err := hasStateRule(state)
+		require.NoError(t, err, "Failed to check XFRM state rule: %v", err)
+		require.True(t, found, "XFRM state rule not found after creation")
+
+		state2 := *state
+		state2.Src = net.ParseIP("10.0.3.100")
+		state2.Dst = net.ParseIP("10.0.4.100")
+		handler.createStateRule(state2.Src, state2.Dst, testKey, ipsecKey)
+		// Verify the state was added
+		found, err = hasStateRule(&state2)
+		require.NoError(t, err, "Failed to check XFRM state rule: %v", err)
+		require.True(t, found, "XFRM state rule not found after creation")
+
+		// Test clean up state
+		handler.Clean(dst.String()) // Clean up the state by passing the destination IP
+
+		// Verify the state was removed
+		found, err = hasStateRule(state)
+		require.NoError(t, err, "Failed to check XFRM state rule: %v", err)
+		require.False(t, found, "XFRM state rule found after deletion")
+		found, err = hasStateRule(&state2)
+		require.NoError(t, err, "Failed to check XFRM state rule: %v", err)
+		require.True(t, found, "XFRM state2 rule should not be removed")
+	})
+}
+
+func hasPolicy(oldPolicy *netlink.XfrmPolicy, out bool) (bool, error) {
+	policies, err := netlink.XfrmPolicyList(netlink.FAMILY_ALL)
+	if err != nil {
+		return false, fmt.Errorf("Failed to list XFRM policies: %v", err)
+	}
+
+	found := false
+	for _, p := range policies {
+		if len(p.Tmpls) == 0 {
+			continue
+		}
+		eq := true
+		eq = eq && (p.Src.String() == oldPolicy.Src.String())
+		eq = eq && (p.Dst.String() == oldPolicy.Dst.String())
+		eq = eq && (p.Tmpls[0].Spi == oldPolicy.Tmpls[0].Spi)
+		eq = eq && (p.Tmpls[0].Src.Equal(oldPolicy.Tmpls[0].Src))
+		eq = eq && (p.Tmpls[0].Dst.Equal(oldPolicy.Tmpls[0].Dst))
+		if out {
+			eq = eq && (p.Dir == netlink.XFRM_DIR_IN)
+		} else {
+			eq = eq && (p.Dir == netlink.XFRM_DIR_OUT)
+		}
+		eq = eq && (p.Tmpls[0].Mode == netlink.XFRM_MODE_TUNNEL)
+		eq = eq && (p.Tmpls[0].Proto == netlink.XFRM_PROTO_ESP)
+		if eq {
+			found = true
+			break
+		}
+	}
+	if !found {
+		return false, nil
+	}
+	return true, nil
+}
+
+// createAndVerifyPolicyRule is a helper function to create and verify an XFRM policy rule.
+func createAndVerifyPolicyRule(t *testing.T, handler *IpSecHandler, policy *netlink.XfrmPolicy, out bool) {
+	// Test ingress policy (out=true)
+	if len(policy.Tmpls) == 0 {
+		t.Fatalf("no templates found in policy")
+	}
+	err := handler.createPolicyRule(policy.Src, policy.Dst, policy.Tmpls[0].Src, policy.Tmpls[0].Dst, policy.Tmpls[0].Spi, out)
+	require.NoError(t, err, "Failed to create policy rule")
+
+	// Verify the policy was added
+	found, err := hasPolicy(policy, out)
+	require.NoError(t, err, "Failed to verify policy")
+	require.True(t, found, "Policy not found")
+}
+
+// TestCreatePolicyRule tests XFRM policy creation logic
+func TestCreatePolicyRule(t *testing.T) {
+	handler := NewIpSecHandler()
+	spi := 1001
+
+	t.Run("test_policy_rule_ingress", func(t *testing.T) { // remote -> local
+		_, srcCIDR, _ := net.ParseCIDR("0.0.0.0/0")     // remote pod CIDR
+		_, dstCIDR, _ := net.ParseCIDR("10.244.1.0/24") // local pod CIDR
+		src := net.ParseIP("10.0.2.100")                // remote nic IP
+		dst := net.ParseIP("10.0.1.100")                // local nic IP
+		policy := &netlink.XfrmPolicy{
+			Src: srcCIDR,
+			Dst: dstCIDR,
+			Dir: netlink.XFRM_DIR_IN,
+			Tmpls: []netlink.XfrmPolicyTmpl{
+				{
+					Src:   src,
+					Dst:   dst,
+					Mode:  netlink.XFRM_MODE_TUNNEL,
+					Proto: netlink.XFRM_PROTO_ESP,
+				},
+			},
+		}
+		// Test ingress policy (out=true)
+		createAndVerifyPolicyRule(t, handler, policy, true)
+		// Test clean up
+		handler.Clean(dst.String())
+		// Verify the policy was removed
+		found, err := hasPolicy(policy, true)
+		if err != nil {
+			t.Errorf("Failed to check has XFRM policies: %v", err)
+			return
+		}
+		if found {
+			t.Errorf("XFRM policy still exists after cleanup: Src=%s, Dst=%s, Spi=%d", policy.Src, policy.Dst, policy.Tmpls[0].Spi)
+		}
+	})
+
+	t.Run("test_policy_rule_egress", func(t *testing.T) {
+		_, srcCIDR, _ := net.ParseCIDR("0.0.0.0/0")     //  local pod CIDR
+		_, dstCIDR, _ := net.ParseCIDR("10.244.1.0/24") // remote pod CIDR
+		src := net.ParseIP("10.0.1.100")                // local nic IP
+		dst := net.ParseIP("10.0.2.100")                // remote nic IP
+		policy := &netlink.XfrmPolicy{
+			Src: srcCIDR,
+			Dst: dstCIDR,
+			Dir: netlink.XFRM_DIR_OUT,
+			Tmpls: []netlink.XfrmPolicyTmpl{
+				{
+					Spi:   spi,
+					Src:   src,
+					Dst:   dst,
+					Mode:  netlink.XFRM_MODE_TUNNEL,
+					Proto: netlink.XFRM_PROTO_ESP,
+				},
+			},
+		}
+		// Test egress policy (out=false)
+		createAndVerifyPolicyRule(t, handler, policy, false)
+
+		// Test clean up
+		handler.Clean(src.String())
+		// Verify the policy was removed
+		found, err := hasPolicy(policy, false)
+		require.NoError(t, err, "Failed to verify policy was removed")
+		require.False(t, found, "Policy was not removed")
+	})
+}
+
+func TestFlush(t *testing.T) {
+	handler := NewIpSecHandler()
+	_, srcCIDR, _ := net.ParseCIDR("0.0.0.0/0")     //  local pod CIDR
+	_, dstCIDR, _ := net.ParseCIDR("10.244.1.0/24") // remote pod CIDR
+	src := net.ParseIP("10.0.1.100")                // local nic IP
+	dst := net.ParseIP("10.0.2.100")                // remote nic IP
+
+	spi := 1001
+	policy := &netlink.XfrmPolicy{
+		Src: srcCIDR,
+		Dst: dstCIDR,
+		Dir: netlink.XFRM_DIR_OUT,
+		Tmpls: []netlink.XfrmPolicyTmpl{
+			{
+				Src:   src,
+				Dst:   dst,
+				Mode:  netlink.XFRM_MODE_TUNNEL,
+				Proto: netlink.XFRM_PROTO_ESP,
+			},
+		},
+	}
+
+	// create state rule
+	testKey := DecodeHex("2dc9410d7cd6b324461bf16db518646594276c5362c30fc476ebca3f1a394b6ed4462161")
+	ipsecKey := IpSecKey{
+		Spi:         1001,
+		AeadKeyName: "rfc4106(gcm(aes))",
+		AeadKey:     testKey,
+		Length:      128,
+	}
+	state := &netlink.XfrmState{
+		Src:   src,
+		Dst:   dst,
+		Proto: netlink.XFRM_PROTO_ESP,
+		Mode:  netlink.XFRM_MODE_TUNNEL,
+		Spi:   ipsecKey.Spi,
+		Reqid: 1,
+		Aead: &netlink.XfrmStateAlgo{
+			Name:   ipsecKey.AeadKeyName,
+			Key:    testKey,
+			ICVLen: ipsecKey.Length,
+		},
+	}
+
+	err := handler.createStateRule(state.Src, state.Dst, testKey, ipsecKey)
+	assert.NoError(t, err, "Failed to add state rule")
+	// Verify the state was added
+	found, err := hasStateRule(state)
+	assert.NoError(t, err, "Failed to find state rule")
+	assert.True(t, found, "State rule not found")
+
+	// Create policy rule
+	createAndVerifyPolicyRule(t, handler, policy, true)
+	policy2 := *policy
+	policy2.Tmpls[0].Src = net.ParseIP("10.0.3.100")
+	policy2.Tmpls[0].Dst = net.ParseIP("10.0.4.100")
+	policy2.Tmpls[0].Spi = spi
+	createAndVerifyPolicyRule(t, handler, &policy2, false)
+
+	// Flush all policies and states
+	err = handler.Flush()
+	require.NoError(t, err, "Failed to flush")
+
+	// Verify all states were removed
+	states, err := netlink.XfrmStateList(netlink.FAMILY_ALL)
+	require.NoError(t, err, "Failed to list states")
+	require.Equal(t, 0, len(states), "XFRM States still exist after flush")
+
+	// Verify all policies were removed
+	policies, err := netlink.XfrmPolicyList(netlink.FAMILY_ALL)
+	require.NoError(t, err, "Failed to list policies")
+
+	require.Equal(t, 0, len(policies), "XFRM policies still exist after flush")
+}
diff --git a/pkg/controller/security/manager_test.go b/pkg/controller/security/manager_test.go
index 98987819..1c46c897 100644
--- a/pkg/controller/security/manager_test.go
+++ b/pkg/controller/security/manager_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/agiledragon/gomonkey/v2"
 	"github.com/stretchr/testify/assert"
 	"istio.io/istio/pkg/security"
+	"istio.io/istio/pkg/test/util/retry"
 
 	camock "kmesh.net/kmesh/pkg/controller/security/mock"
 )
@@ -124,7 +125,7 @@ func runTestCertRotate(t *testing.T) {
 	for {
 		secretManager.certsCache.mu.RLock()
 		cert2 := secretManager.certsCache.certs[identity1]
-		if cert2 != nil && cert2.cert.CreatedTime != oldCert.CreatedTime {
+		if cert2 != nil && cert2.cert != nil && cert2.cert.CreatedTime != oldCert.CreatedTime {
 			newCert = *cert2.cert
 			secretManager.certsCache.mu.RUnlock()
 			break
@@ -160,13 +161,32 @@ func runTestretryFetchCert(t *testing.T) {
 
 	go secretManager.Run(stopCh)
 	identity := "identity"
-	identity1 := "identity1"
 	secretManager.SendCertRequest(identity, ADD)
 	time.Sleep(100 * time.Millisecond)
 	patches2.Reset()
-	secretManager.SendCertRequest(identity1, RETRY)
-	time.Sleep(2000 * time.Millisecond)
-	assert.NotNil(t, secretManager.GetCert(identity).cert)
+
+	secretManager.SendCertRequest(identity, RETRY)
+
+	err = retry.UntilSuccess(
+		func() error {
+			cert := secretManager.GetCert(identity)
+			if cert != nil {
+				secretManager.certsCache.mu.RLock()
+				hasCert := cert.cert != nil
+				secretManager.certsCache.mu.RUnlock()
+				if hasCert {
+					return nil
+				}
+			}
+			return fmt.Errorf("cert not found for identity %s", identity)
+		},
+		retry.Delay(100*time.Millisecond),
+		retry.Timeout(6*time.Second),
+	)
+
+	if err != nil {
+		t.Errorf("Failed to fetch cert after retry: %v", err)
+	}
 
 	close(stopCh)
 }
diff --git a/pkg/status/status_server_test.go b/pkg/status/status_server_test.go
index dea4b731..39141730 100644
--- a/pkg/status/status_server_test.go
+++ b/pkg/status/status_server_test.go
@@ -133,11 +133,11 @@ func TestServer_setLoggerLevel(t *testing.T) {
 	}
 }
 
-func TestServer_configDumpWorkload(t *testing.T) {
-	w1 := &workloadapi.Workload{
+func buildWorkload(name string) *workloadapi.Workload {
+	return &workloadapi.Workload{
 		Uid:               "cluster0//Pod/ns/name",
 		Namespace:         "ns",
-		Name:              "name",
+		Name:              name,
 		Addresses:         [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
 		Network:           "testnetwork",
 		CanonicalName:     "foo",
@@ -173,10 +173,13 @@ func TestServer_configDumpWorkload(t *testing.T) {
 			},
 		},
 	}
-	svc := &workloadapi.Service{
-		Name:      "svc",
+}
+
+func buildService(name, hostname string) *workloadapi.Service {
+	return &workloadapi.Service{
+		Name:      name,
 		Namespace: "ns",
-		Hostname:  "hostname",
+		Hostname:  hostname,
 		Ports: []*workloadapi.Port{
 			{
 				ServicePort: 80,
@@ -199,6 +202,12 @@ func TestServer_configDumpWorkload(t *testing.T) {
 				},
 			},
 		}}
+}
+
+func TestServer_configDumpWorkload(t *testing.T) {
+	w := buildWorkload("name")
+	svc := buildService("svc", "hostname")
+
 	policy := &security.Authorization{
 		Name:      "policy",
 		Namespace: "ns",
@@ -207,7 +216,7 @@ func TestServer_configDumpWorkload(t *testing.T) {
 	}
 	fakeWorkloadCache := cache.NewWorkloadCache()
 	fakeServiceCache := cache.NewServiceCache()
-	fakeWorkloadCache.AddOrUpdateWorkload(w1)
+	fakeWorkloadCache.AddOrUpdateWorkload(w)
 	fakeServiceCache.AddOrUpdateService(svc)
 	fakeAuth := auth.NewRbac(fakeWorkloadCache)
 	fakeAuth.UpdatePolicy(policy)
@@ -225,20 +234,123 @@ func TestServer_configDumpWorkload(t *testing.T) {
 	}
 
 	// Create a new HTTP request and response
-	req := httptest.NewRequest(http.MethodGet, "/configDumpWorkload", nil)
-	w := httptest.NewRecorder()
+	req1 := httptest.NewRequest(http.MethodGet, "/configDumpWorkload", nil)
+	w1 := httptest.NewRecorder()
 
 	// Call the configDumpWorkload function
-	server.configDumpWorkload(w, req)
+	server.configDumpWorkload(w1, req1)
 
 	// Check the response status code
-	if w.Code != http.StatusOK {
-		t.Errorf("Expected status code %d, but got %d", http.StatusOK, w.Code)
+	if w1.Code != http.StatusOK {
+		t.Errorf("Expected status code %d, but got %d", http.StatusOK, w1.Code)
+	}
+
+	util.RefreshGoldenFile(t, w1.Body.Bytes(), "./testdata/workload_configdump.json")
+
+	util.CompareContent(t, w1.Body.Bytes(), "./testdata/workload_configdump.json")
+
+	fakeWorkloadCache = cache.NewWorkloadCache()
+	fakeServiceCache = cache.NewServiceCache()
+
+	workloads := []*workloadapi.Workload{}
+	services := []*workloadapi.Service{}
+
+	for i := 0; i < 10; i++ {
+		w := buildWorkload(fmt.Sprintf("workload-%d", i))
+		w.Uid = fmt.Sprintf("cluster0//Pod/ns/workload-%d", i)
+		workloads = append(workloads, w)
+		svc := buildService(fmt.Sprintf("service-%d", i), fmt.Sprintf("hostname-%d", i))
+		services = append(services, svc)
+
+		fakeWorkloadCache.AddOrUpdateWorkload(w)
+		fakeServiceCache.AddOrUpdateService(svc)
 	}
 
-	util.RefreshGoldenFile(t, w.Body.Bytes(), "./testdata/workload_configdump.json")
+	// Create a new HTTP response
+	w2 := httptest.NewRecorder()
+
+	server = &Server{
+		xdsClient: &controller.XdsClient{
+			WorkloadController: &workload.Controller{
+				Processor: &workload.Processor{
+					WorkloadCache: fakeWorkloadCache,
+					ServiceCache:  fakeServiceCache,
+				},
+				Rbac: fakeAuth,
+			},
+		},
+	}
+
+	// Call the configDumpWorkload function
+	server.configDumpWorkload(w2, req1)
+
+	// Check the response status code
+	if w2.Code != http.StatusOK {
+		t.Errorf("Expected status code %d, but got %d", http.StatusOK, w2.Code)
+	}
+
+	util.RefreshGoldenFile(t, w2.Body.Bytes(), "./testdata/workload_configdump_original_sorted.json")
+	util.CompareContent(t, w2.Body.Bytes(), "./testdata/workload_configdump_original_sorted.json")
+
+	fakeWorkloadCache = cache.NewWorkloadCache()
+	fakeServiceCache = cache.NewServiceCache()
+	// Modify workloads and services properties
+	for i := 0; i < 5; i++ { // Modify first 5 items
+		// Modify workload properties
+		w := buildWorkload(fmt.Sprintf("workload-%d-modified", i))
+		w.ClusterId = "cluster1" // Changed cluster
+		w.Uid = fmt.Sprintf("cluster1//Pod/ns/workload-%d-modified", i)
+		w.Status = workloadapi.WorkloadStatus_UNHEALTHY
+
+		workloads[i] = w
+		// Modify service properties
+		svc := buildService(fmt.Sprintf("service-%d-modified", i), fmt.Sprintf("hostname-%d-modified", i))
+		// Modify service ports
+		svc.Ports = []*workloadapi.Port{
+			{
+				ServicePort: 90,
+				TargetPort:  9090,
+			},
+			{
+				ServicePort: 91,
+				TargetPort:  0,
+			},
+			{
+				ServicePort: 92,
+				TargetPort:  0,
+			},
+		}
+		services[i] = svc
+	}
+	for _, w := range workloads {
+		fakeWorkloadCache.AddOrUpdateWorkload(w)
+	}
+	for _, svc := range services {
+		fakeServiceCache.AddOrUpdateService(svc)
+	}
+
+	w3 := httptest.NewRecorder()
+
+	server = &Server{
+		xdsClient: &controller.XdsClient{
+			WorkloadController: &workload.Controller{
+				Processor: &workload.Processor{
+					WorkloadCache: fakeWorkloadCache,
+					ServiceCache:  fakeServiceCache,
+				},
+				Rbac: fakeAuth,
+			},
+		},
+	}
+
+	server.configDumpWorkload(w3, req1)
+
+	if w3.Code != http.StatusOK {
+		t.Errorf("Expected status code %d, but got %d", http.StatusOK, w3.Code)
+	}
 
-	util.CompareContent(t, w.Body.Bytes(), "./testdata/workload_configdump.json")
+	util.RefreshGoldenFile(t, w3.Body.Bytes(), "./testdata/workload_configdump_modified_sorted.json")
+	util.CompareContent(t, w3.Body.Bytes(), "./testdata/workload_configdump_modified_sorted.json")
 }
 
 func TestServer_dumpWorkloadBpfMap(t *testing.T) {
diff --git a/pkg/status/testdata/workload_configdump_modified_sorted.json b/pkg/status/testdata/workload_configdump_modified_sorted.json
new file mode 100644
index 00000000..c983043b
--- /dev/null
+++ b/pkg/status/testdata/workload_configdump_modified_sorted.json
@@ -0,0 +1,495 @@
+{
+    "workloads": [
+        {
+            "uid": "cluster1//Pod/ns/workload-0-modified",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-0-modified",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster1",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "UNHEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster1//Pod/ns/workload-1-modified",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-1-modified",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster1",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "UNHEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster1//Pod/ns/workload-2-modified",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-2-modified",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster1",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "UNHEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster1//Pod/ns/workload-3-modified",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-3-modified",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster1",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "UNHEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster1//Pod/ns/workload-4-modified",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-4-modified",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster1",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "UNHEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-5",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-5",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-6",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-6",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-7",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-7",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-8",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-8",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-9",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-9",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        }
+    ],
+    "services": [
+        {
+            "name": "service-0-modified",
+            "namespace": "ns",
+            "hostname": "hostname-0-modified",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 90,
+                    "target_port": 9090
+                },
+                {
+                    "service_port": 91
+                },
+                {
+                    "service_port": 92
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-1-modified",
+            "namespace": "ns",
+            "hostname": "hostname-1-modified",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 90,
+                    "target_port": 9090
+                },
+                {
+                    "service_port": 91
+                },
+                {
+                    "service_port": 92
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-2-modified",
+            "namespace": "ns",
+            "hostname": "hostname-2-modified",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 90,
+                    "target_port": 9090
+                },
+                {
+                    "service_port": 91
+                },
+                {
+                    "service_port": 92
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-3-modified",
+            "namespace": "ns",
+            "hostname": "hostname-3-modified",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 90,
+                    "target_port": 9090
+                },
+                {
+                    "service_port": 91
+                },
+                {
+                    "service_port": 92
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-4-modified",
+            "namespace": "ns",
+            "hostname": "hostname-4-modified",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 90,
+                    "target_port": 9090
+                },
+                {
+                    "service_port": 91
+                },
+                {
+                    "service_port": 92
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-5",
+            "namespace": "ns",
+            "hostname": "hostname-5",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-6",
+            "namespace": "ns",
+            "hostname": "hostname-6",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-7",
+            "namespace": "ns",
+            "hostname": "hostname-7",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-8",
+            "namespace": "ns",
+            "hostname": "hostname-8",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-9",
+            "namespace": "ns",
+            "hostname": "hostname-9",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        }
+    ],
+    "policies": [
+        {
+            "name": "policy",
+            "namespace": "ns",
+            "scope": "GLOBAL",
+            "action": "ALLOW",
+            "rules": null
+        }
+    ]
+}
\ No newline at end of file
diff --git a/pkg/status/testdata/workload_configdump_original_sorted.json b/pkg/status/testdata/workload_configdump_original_sorted.json
new file mode 100644
index 00000000..40834898
--- /dev/null
+++ b/pkg/status/testdata/workload_configdump_original_sorted.json
@@ -0,0 +1,495 @@
+{
+    "workloads": [
+        {
+            "uid": "cluster0//Pod/ns/workload-0",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-0",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-1",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-1",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-2",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-2",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-3",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-3",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-4",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-4",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-5",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-5",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-6",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-6",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-7",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-7",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-8",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-8",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        },
+        {
+            "uid": "cluster0//Pod/ns/workload-9",
+            "addresses": [
+                "1.2.3.4"
+            ],
+            "waypoint": "testnetwork/192.168.1.10",
+            "protocol": "NONE",
+            "name": "workload-9",
+            "namespace": "ns",
+            "serviceAccount": "",
+            "workloadName": "name",
+            "workloadType": "POD",
+            "canonicalName": "foo",
+            "canonicalRevision": "latest",
+            "clusterId": "cluster0",
+            "locality": {},
+            "node": "",
+            "network": "testnetwork",
+            "status": "HEALTHY",
+            "applicationTunnel": {
+                "protocol": ""
+            },
+            "services": [
+                "ns/hostname"
+            ]
+        }
+    ],
+    "services": [
+        {
+            "name": "service-0",
+            "namespace": "ns",
+            "hostname": "hostname-0",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-1",
+            "namespace": "ns",
+            "hostname": "hostname-1",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-2",
+            "namespace": "ns",
+            "hostname": "hostname-2",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-3",
+            "namespace": "ns",
+            "hostname": "hostname-3",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-4",
+            "namespace": "ns",
+            "hostname": "hostname-4",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-5",
+            "namespace": "ns",
+            "hostname": "hostname-5",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-6",
+            "namespace": "ns",
+            "hostname": "hostname-6",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-7",
+            "namespace": "ns",
+            "hostname": "hostname-7",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-8",
+            "namespace": "ns",
+            "hostname": "hostname-8",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        },
+        {
+            "name": "service-9",
+            "namespace": "ns",
+            "hostname": "hostname-9",
+            "vips": [],
+            "ports": [
+                {
+                    "service_port": 80,
+                    "target_port": 8080
+                },
+                {
+                    "service_port": 81
+                },
+                {
+                    "service_port": 82
+                }
+            ],
+            "loadBalancer": null,
+            "waypoint": {
+                "destination": "testnetwork/192.168.1.11"
+            }
+        }
+    ],
+    "policies": [
+        {
+            "name": "policy",
+            "namespace": "ns",
+            "scope": "GLOBAL",
+            "action": "ALLOW",
+            "rules": null
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/unittest/workload/Makefile b/test/unittest/workload/Makefile
deleted file mode 100644
index 1b1595df..00000000
--- a/test/unittest/workload/Makefile
+++ /dev/null
@@ -1,27 +0,0 @@
-CFLAGS += -g3 -Wall -fPIC -I. -D__TARGET_ARCH_x86_64 -D__x86_64__ -D_GNU_SOURCE
-CFLAGS += -isystem /usr/include
-LIBS = bpf
-
-WORKLOAD_DIR = ../../../bpf/kmesh/workload
-TEST_DIR = .
-INCLUDE_DIR = $(WORKLOAD_DIR)/../../include
-
-FLAGS += -I$(WORKLOAD_DIR) -I../../../bpf/kmesh/workload/include/ctx -I../../../bpf/kmesh/workload/include/ -I../../../bpf/probes/ -I$(INCLUDE_DIR)
-
-all: xdp_test
-
-xdp_test: $(TEST_DIR)/xdp.bpf.o $(TEST_DIR)/xdp_test.skel.h
-	gcc $(CFLAGS) $(FLAGS) -include /usr/include/errno.h -o $(TEST_DIR)/xdp_test $(TEST_DIR)/xdp_test.c -l$(LIBS)
-
-$(TEST_DIR)/xdp.bpf.o: $(WORKLOAD_DIR)/xdp.c
-	clang -target bpf $(CFLAGS) $(FLAGS) -O2 -g -c $< -o $@
-
-$(TEST_DIR)/xdp_test.skel.h: $(TEST_DIR)/xdp.bpf.o
-	bpftool gen skeleton $< > $@
-
-.PHONY: clean
-clean:
-	rm -rf $(TEST_DIR)/xdp.bpf.o
-	rm -rf $(TEST_DIR)/xdp_test.skel.h 
-	rm -rf $(TEST_DIR)/xdp_test
-	rm -rf preprocessed_output.c
\ No newline at end of file
diff --git a/test/unittest/workload/common.h b/test/unittest/workload/common.h
deleted file mode 100644
index 83385a5a..00000000
--- a/test/unittest/workload/common.h
+++ /dev/null
@@ -1,160 +0,0 @@
-#ifndef __TEST_COMMON_H
-#define __TEST_COMMON_H
-
-#include <stdio.h>
-#include <assert.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-#include <time.h>
-// Test result codes
-#define TEST_PASS  0
-#define TEST_FAIL  1
-#define TEST_SKIP  2
-#define TEST_ERROR 3
-
-// Coverage related paths
-#define COVERAGE_PROG_PIN_DIR "/sys/fs/bpf/prog"
-#define COVERAGE_MAP_PIN_DIR  "/sys/fs/bpf/map"
-#define COVERAGE_BLOCK_LIST   "/tmp/block.list"
-#define COVERAGE_OUTPUT       "/tmp/coverage.html"
-
-#define MAX_SUBTESTS 64
-
-typedef enum {
-    TEST_STATUS_NONE = 0,
-    TEST_STATUS_RUNNING,
-    TEST_STATUS_COMPLETED,
-    TEST_STATUS_FAILED,
-    TEST_STATUS_SKIPPED
-} test_status_t;
-
-typedef struct {
-    const char *name;
-    test_status_t status;
-    int result;
-    const char *message;
-    double duration; // Test execution time in seconds
-} test_context_t;
-
-// Global test state
-typedef struct {
-    const char *suite_name;
-    test_context_t subtests[MAX_SUBTESTS];
-    int subtest_count;
-    int passed_count;
-    int failed_count;
-    int skipped_count;
-} test_suite_t;
-
-static test_suite_t current_suite = {0};
-static test_context_t *current_test_ctx = NULL;
-
-static inline void test_init(const char *test_name)
-{
-    printf("\n=== Starting test suite: %s ===\n", test_name);
-    current_suite.suite_name = test_name;
-    current_suite.subtest_count = 0;
-    current_suite.passed_count = 0;
-    current_suite.failed_count = 0;
-    current_suite.skipped_count = 0;
-}
-
-static inline void test_finish(void)
-{
-    printf("\n=== Test suite summary: %s ===\n", current_suite.suite_name);
-    printf("Total tests: %d\n", current_suite.subtest_count);
-    printf("  Passed:  %d\n", current_suite.passed_count);
-    printf("  Failed:  %d\n", current_suite.failed_count);
-    printf("  Skipped: %d\n", current_suite.skipped_count);
-
-    if (current_suite.subtest_count > 0) {
-        printf("\nDetailed results:\n");
-        for (int i = 0; i < current_suite.subtest_count; i++) {
-            test_context_t *test = &current_suite.subtests[i];
-            const char *status_str = test->result == TEST_PASS ? "PASS" :
-                                     test->result == TEST_SKIP ? "SKIP" :
-                                     test->result == TEST_FAIL ? "FAIL" :
-                                                                 "ERROR";
-
-            printf("  %s: %s (%.3fs)", test->name, status_str, test->duration);
-            if (test->message) {
-                printf(" - %s", test->message);
-            }
-            printf("\n");
-        }
-    }
-
-    printf("\n=== Final result: %s ===\n\n", current_suite.failed_count > 0 ? "FAILED" : "PASSED");
-}
-
-#define TEST(test_name, fn)                                                                                            \
-    do {                                                                                                               \
-        const char *_test_name = test_name;                                                                            \
-        if (current_suite.subtest_count >= MAX_SUBTESTS) {                                                             \
-            printf("ERROR: Too many subtests\n");                                                                      \
-            break;                                                                                                     \
-        }                                                                                                              \
-        test_context_t *_test_ctx = &current_suite.subtests[current_suite.subtest_count++];                            \
-        _test_ctx->name = _test_name;                                                                                  \
-        _test_ctx->status = TEST_STATUS_RUNNING;                                                                       \
-        _test_ctx->result = TEST_PASS;                                                                                 \
-        _test_ctx->message = NULL;                                                                                     \
-        current_test_ctx = _test_ctx; /* Set current test context */                                                   \
-        struct timespec _start_time, _end_time;                                                                        \
-        clock_gettime(CLOCK_MONOTONIC, &_start_time);                                                                  \
-        test_log("\n--- Starting test: %s ---", _test_name);                                                           \
-        fn();                                                                                                          \
-        clock_gettime(CLOCK_MONOTONIC, &_end_time);                                                                    \
-        _test_ctx->duration =                                                                                          \
-            (_end_time.tv_sec - _start_time.tv_sec) + (_end_time.tv_nsec - _start_time.tv_nsec) / 1e9;                 \
-        _test_ctx->status = TEST_STATUS_COMPLETED;                                                                     \
-        switch (_test_ctx->result) {                                                                                   \
-        case TEST_PASS:                                                                                                \
-            current_suite.passed_count++;                                                                              \
-            break;                                                                                                     \
-        case TEST_FAIL:                                                                                                \
-            current_suite.failed_count++;                                                                              \
-            break;                                                                                                     \
-        case TEST_SKIP:                                                                                                \
-            current_suite.skipped_count++;                                                                             \
-            break;                                                                                                     \
-        }                                                                                                              \
-        test_log(                                                                                                      \
-            "--- Test %s: %s ---\n",                                                                                   \
-            _test_ctx->name,                                                                                           \
-            _test_ctx->result == TEST_PASS ? "PASSED" :                                                                \
-            _test_ctx->result == TEST_SKIP ? "SKIPPED" :                                                               \
-                                             "FAILED");                                                                \
-        current_test_ctx = NULL; /* Clear current test context */                                                      \
-    } while (0)
-
-#define SKIP_SUB_TEST(msg)                                                                                             \
-    do {                                                                                                               \
-        test_log("Skipping test: %s", msg);                                                                            \
-        current_test_ctx->result = TEST_SKIP;                                                                          \
-        current_test_ctx->message = msg;                                                                               \
-        break;                                                                                                         \
-    } while (0)
-
-#define test_assert(cond, msg)                                                                                         \
-    do {                                                                                                               \
-        if (!(cond)) {                                                                                                 \
-            test_log("Assert failed: %s", msg);                                                                        \
-            test_log("At %s:%d", __FILE__, __LINE__);                                                                  \
-            if (current_test_ctx) {                                                                                    \
-                current_test_ctx->result = TEST_FAIL;                                                                  \
-                current_test_ctx->message = msg;                                                                       \
-            }                                                                                                          \
-            return;                                                                                                    \
-        }                                                                                                              \
-    } while (0)
-
-// Test logging
-#define test_log(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
-
-// Test setup/teardown helpers
-typedef void (*test_setup_fn)(void);
-typedef void (*test_teardown_fn)(void);
-
-#endif /* __TEST_COMMON_H */
\ No newline at end of file
diff --git a/test/unittest/workload/run_tests.sh b/test/unittest/workload/run_tests.sh
deleted file mode 100644
index 62de9d6c..00000000
--- a/test/unittest/workload/run_tests.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/bash
-
-# Exit on error
-set -e
-
-# Ensure running as root
-if [ "$EUID" -ne 0 ]; then
-	echo "Please run as root"
-	exit 1
-fi
-
-# Set memory limits
-ulimit -l unlimited
-
-# Create necessary directories
-mkdir -p /sys/fs/bpf/prog
-mkdir -p /sys/fs/bpf/map
-mkdir -p /tmp/coverage
-
-# Define paths
-ELF_FILE="xdp.bpf.o"
-PROG_PIN_DIR="/sys/fs/bpf/prog"
-MAP_PIN_DIR="/sys/fs/bpf/map"
-BLOCK_LIST="/tmp/coverage/block.list"
-COVERAGE_OUTPUT="/tmp/coverage/coverage.html"
-
-# Clean up old pins
-rm -rf ${PROG_PIN_DIR}/* ${MAP_PIN_DIR}/* || true
-
-# Clean and rebuild
-echo "Cleaning and rebuilding..."
-make clean
-make
-
-# 1. Load and instrument BPF program
-echo "Loading and instrumenting BPF program..."
-coverbee load \
-	--elf=${ELF_FILE} \
-	--prog-pin-dir=${PROG_PIN_DIR} \
-	--map-pin-dir=${MAP_PIN_DIR} \
-	--block-list=${BLOCK_LIST} \
-	--log=/tmp/coverage/coverbee.log
-
-# 2. Run tests
-echo "Running tests..."
-./xdp_test
-
-# 3. Collect coverage data
-echo "Collecting coverage data..."
-coverbee cover \
-	--map-pin-dir=${MAP_PIN_DIR} \
-	--block-list=${BLOCK_LIST} \
-	--output=${COVERAGE_OUTPUT} \
-	--format=html
-
-# 4. Cleanup
-echo "Cleaning up..."
-rm -rf ${PROG_PIN_DIR}/*
-rm -rf ${MAP_PIN_DIR}/*
-
-echo "Coverage report generated at: ${COVERAGE_OUTPUT}"
diff --git a/test/unittest/workload/xdp_test.c b/test/unittest/workload/xdp_test.c
deleted file mode 100644
index 104d4cd6..00000000
--- a/test/unittest/workload/xdp_test.c
+++ /dev/null
@@ -1,310 +0,0 @@
-#include <errno.h>
-#include <bpf/bpf.h>
-#include <bpf/libbpf.h>
-#include <stdio.h>
-#include <assert.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <linux/if_ether.h>
-#include <linux/ip.h>
-#include <linux/ipv6.h>
-#include <linux/tcp.h>
-#include "xdp_test.skel.h"
-#include <asm/unistd.h>
-
-#include "common.h"
-
-// Add missing includes and definitions
-#include <unistd.h>
-#include <linux/if_xdp.h>
-#include <linux/if.h>
-#include <linux/bpf.h>
-
-#define XDP_PACKET_HEADROOM 256
-#define SKB_DATA_ALIGN(len) (((len) + (64 - 1)) & ~(64 - 1))
-#define AUTH_PASS           0
-#define AUTH_FORBID         1
-
-// Define skb_shared_info structure (simplified version)
-struct skb_shared_info {
-    unsigned char pad[256]; // Simplified version
-};
-
-// Define xdp_buff structure
-struct xdp_buff {
-    void *data;
-    void *data_end;
-    void *data_meta;
-    void *data_hard_start;
-    unsigned int frame_sz;
-};
-
-#define PACKET_SIZE 100
-
-struct xdp_bpf *skel;
-int prog_fd;
-
-static int run_xdp_test(void *packet, size_t size)
-{
-    // Constants from kernel implementation
-    unsigned int headroom = XDP_PACKET_HEADROOM; // 256
-    unsigned int tailroom = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
-    unsigned int max_data_sz = 4096 - headroom - tailroom;
-
-    if (size > max_data_sz) {
-        test_log("Packet size too large: %zu > %u", size, max_data_sz);
-        return -1;
-    }
-
-    // Allocate memory for the full frame
-    void *data;
-    size_t frame_size = headroom + size + tailroom;
-    if (posix_memalign(&data, getpagesize(), frame_size) != 0) {
-        test_log("Failed to allocate memory for test data");
-        return -1;
-    }
-
-    // Initialize the memory to zero
-    memset(data, 0, frame_size);
-
-    // Copy packet data to the correct location (after headroom)
-    memcpy(data + headroom, packet, size);
-    struct xdp_buff xdp = {
-        .data_hard_start = data,
-        .data = data + headroom,
-        .data_meta = data + headroom,
-        .data_end = data + headroom + size,
-        .frame_sz = frame_size,
-    };
-
-    test_log("Debug info:");
-    test_log("  frame_size: %zu", frame_size);
-    test_log("  headroom: %u", headroom);
-    test_log("  data_size: %zu", size);
-    test_log("  tailroom: %u", tailroom);
-    test_log("  hard_start: %p", xdp.data_hard_start);
-    test_log("  data: %p", xdp.data);
-    test_log("  data_end: %p", xdp.data_end);
-    test_log("  frame_sz: %u", xdp.frame_sz);
-
-    struct bpf_test_run_opts opts = {
-        .sz = sizeof(struct bpf_test_run_opts),
-        .data_in = data + headroom,
-        .data_out = data + headroom,
-        .data_size_in = size,
-        .data_size_out = size,
-        // XDP programs don't use ctx_in/ctx_out according to kernel code
-        .ctx_in = NULL,
-        .ctx_out = NULL,
-        .ctx_size_in = 0,
-        .ctx_size_out = 0,
-        .repeat = 1,
-    };
-
-    int ret = bpf_prog_test_run_opts(prog_fd, &opts);
-    if (ret != 0) {
-        test_log("bpf_prog_test_run_opts failed: %d (errno: %d - %s)", ret, errno, strerror(errno));
-        test_log("Data size: %u, Frame size: %zu", opts.data_size_in, frame_size);
-        test_log("Prog FD: %d", prog_fd);
-        test_log("Retval: %d", opts.retval);
-    } else {
-        test_log("Test run successful, retval: %d", opts.retval);
-    }
-
-    free(data);
-    memcpy(packet, opts.data_out, opts.data_size_out);
-    return ret;
-}
-
-void bpf_offload()
-{
-    xdp_bpf__destroy(skel);
-}
-
-void test_packet_parsing()
-{
-    unsigned char packet[PACKET_SIZE] = {0};
-    struct ethhdr *eth = (struct ethhdr *)packet;
-    struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr));
-    struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr));
-
-    // Set minimum packet size
-    size_t min_size = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct tcphdr);
-    test_assert(PACKET_SIZE >= min_size, "PACKET_SIZE too small");
-
-    // Fill in packet headers
-    eth->h_proto = htons(ETH_P_IP);
-    memset(eth->h_source, 0x12, ETH_ALEN);
-    memset(eth->h_dest, 0x34, ETH_ALEN);
-
-    ip->version = 4;
-    ip->ihl = 5;
-    ip->protocol = IPPROTO_TCP;
-    ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr));
-    ip->daddr = htonl(0x08080808); // 8.8.8.8
-    ip->saddr = htonl(0x0A000001); // 10.0.0.1
-
-    tcp->source = htons(12345);
-    tcp->dest = htons(80);
-    tcp->doff = 5; // 20 bytes TCP header
-
-    test_log("Packet size: %u", PACKET_SIZE);
-    test_log("Headers size: %u", min_size);
-
-    int err = run_xdp_test(packet, PACKET_SIZE);
-    test_log("err = %d", err);
-    test_assert(err == 0, "run_xdp_test failed");
-}
-
-void bpf_load()
-{
-    int err;
-
-    skel = xdp_bpf__open();
-    test_assert(skel != NULL, "Failed to open BPF skeleton");
-
-    // Set XDP program type
-    bpf_program__set_type(skel->progs.xdp_shutdown, BPF_PROG_TYPE_XDP);
-
-    err = xdp_bpf__load(skel);
-    test_assert(err == 0, "Failed to load BPF skeleton");
-
-    prog_fd = bpf_program__fd(skel->progs.xdp_shutdown);
-    test_assert(prog_fd >= 0, "Failed to get program FD");
-
-    // Get program type and expected test parameters
-    struct bpf_prog_info info = {};
-    __u32 info_len = sizeof(info);
-    err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len);
-    test_assert(err == 0, "Failed to get program info");
-
-    test_log("Successfully loaded XDP program, prog_fd = %d, type = %u", prog_fd, info.type);
-}
-
-void test_ip_version_check()
-{
-    unsigned char packet[PACKET_SIZE] = {0};
-    struct ethhdr *eth = (struct ethhdr *)packet;
-    struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr));
-
-    eth->h_proto = htons(ETH_P_IP);
-    ip->version = 5; // Invalid IP version
-
-    int err = run_xdp_test(packet, PACKET_SIZE);
-    test_assert(err == 0, "run_xdp_test failed");
-}
-
-void test_tuple_extraction()
-{
-    unsigned char packet[PACKET_SIZE] = {0};
-    struct ethhdr *eth = (struct ethhdr *)packet;
-    struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr));
-    struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr));
-
-    eth->h_proto = htons(ETH_P_IP);
-    ip->version = 4;
-    ip->ihl = 5;
-    ip->protocol = IPPROTO_TCP;
-    ip->saddr = inet_addr("192.168.1.1");
-    ip->daddr = inet_addr("192.168.1.2");
-    tcp->source = htons(12345);
-    tcp->dest = htons(80);
-
-    int err = run_xdp_test(packet, PACKET_SIZE);
-    test_assert(err == 0, "run_xdp_test failed");
-}
-
-void test_connection_shutdown()
-{
-    unsigned char packet[PACKET_SIZE] = {0};
-    struct ethhdr *eth = (struct ethhdr *)packet;
-    struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr));
-    struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr));
-
-    // Set minimum packet size
-    size_t min_size = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct tcphdr);
-    test_assert(PACKET_SIZE >= min_size, "PACKET_SIZE too small");
-
-    // Fill in ethernet header
-    eth->h_proto = htons(ETH_P_IP);
-    memset(eth->h_source, 0x12, ETH_ALEN);
-    memset(eth->h_dest, 0x34, ETH_ALEN);
-
-    // Fill in IP header
-    ip->version = 4;
-    ip->ihl = 5;
-    ip->protocol = IPPROTO_TCP;
-    ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr));
-    ip->saddr = inet_addr("192.168.1.1");
-    ip->daddr = inet_addr("192.168.1.2");
-
-    // Fill in TCP header
-    tcp->source = htons(12345);
-    tcp->dest = htons(80);
-    tcp->doff = 5; // 20 bytes TCP header
-    tcp->syn = 1;  // SYN packet
-    tcp->rst = 0;  // Make sure RST is not set initially
-
-    // Add the connection to the auth map to simulate a connection that should be shut down
-    struct bpf_sock_tuple tuple = {
-        .ipv4 =
-            {
-                .saddr = ip->saddr,
-                .daddr = ip->daddr,
-                .sport = tcp->source,
-                .dport = tcp->dest,
-            },
-    };
-    __u32 value = AUTH_FORBID; // Use AUTH_FORBID value
-
-    // Update the map
-    int err = bpf_map_update_elem(bpf_map__fd(skel->maps.map_of_auth_result), &tuple, &value, BPF_ANY);
-    test_assert(err == 0, "Failed to update map");
-
-    // Log test details
-    test_log("Testing connection shutdown:");
-    test_log("  Source IP: %s", inet_ntoa((struct in_addr){.s_addr = ip->saddr}));
-    test_log("  Dest IP: %s", inet_ntoa((struct in_addr){.s_addr = ip->daddr}));
-    test_log("  Source Port: %d", ntohs(tcp->source));
-    test_log("  Dest Port: %d", ntohs(tcp->dest));
-    test_log("  Map value: %u", value);
-
-    // Run the XDP program
-    int err1 = run_xdp_test(packet, PACKET_SIZE);
-    test_assert(err1 == 0, "XDP test failed");
-
-    // Check if the packet was modified correctly
-    struct tcphdr *modified_tcp = (struct tcphdr *)(packet + sizeof(struct ethhdr) + sizeof(struct iphdr));
-    test_log("TCP flags after XDP:");
-    test_log("  RST: %d", modified_tcp->rst);
-    test_log("  SYN: %d", modified_tcp->syn);
-    test_log("  FIN: %d", modified_tcp->fin);
-    test_log("  PSH: %d", modified_tcp->psh);
-    test_log("  ACK: %d", modified_tcp->ack);
-
-    // Verify that RST flag was set
-    test_assert(modified_tcp->rst == 1, "RST flag not set");
-    test_assert(modified_tcp->syn == 0, "SYN flag not cleared"); // SYN should be cleared
-    test_assert(modified_tcp->fin == 0, "FIN flag not cleared"); // FIN should be cleared
-    test_assert(modified_tcp->psh == 0, "PSH flag not cleared"); // PSH should be cleared
-    test_assert(modified_tcp->ack == 0, "ACK flag not cleared"); // ACK should be cleared
-
-    // Clean up the map entry
-    bpf_map_delete_elem(bpf_map__fd(skel->maps.map_of_auth_result), &tuple);
-}
-
-int main()
-{
-    test_init("xdp_test");
-
-    TEST("BPF Program Load", bpf_load);
-    TEST("Packet Parsing", test_packet_parsing);
-    TEST("IP Version Check", test_ip_version_check);
-    TEST("Tuple Extraction", test_tuple_extraction);
-    TEST("Connection Shutdown", test_connection_shutdown);
-    TEST("BPF Program Cleanup", bpf_offload);
-
-    test_finish();
-    return current_suite.failed_count > 0 ? 1 : 0;
-}
\ No newline at end of file