mock-proxy 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. mock_proxy-0.1.0/.gitignore +41 -0
  2. mock_proxy-0.1.0/PKG-INFO +180 -0
  3. mock_proxy-0.1.0/README.md +160 -0
  4. mock_proxy-0.1.0/client/__init__.py +1 -0
  5. mock_proxy-0.1.0/client/agent.py +129 -0
  6. mock_proxy-0.1.0/client/dns_watcher.py +47 -0
  7. mock_proxy-0.1.0/client/iptables.py +94 -0
  8. mock_proxy-0.1.0/client/ws_client.py +71 -0
  9. mock_proxy-0.1.0/pyproject.toml +56 -0
  10. mock_proxy-0.1.0/server/__init__.py +1 -0
  11. mock_proxy-0.1.0/server/app.py +162 -0
  12. mock_proxy-0.1.0/server/cli.py +30 -0
  13. mock_proxy-0.1.0/server/config.py +16 -0
  14. mock_proxy-0.1.0/server/hub/__init__.py +1 -0
  15. mock_proxy-0.1.0/server/hub/database.py +61 -0
  16. mock_proxy-0.1.0/server/hub/routers/__init__.py +1 -0
  17. mock_proxy-0.1.0/server/hub/routers/agents.py +11 -0
  18. mock_proxy-0.1.0/server/hub/routers/rules.py +113 -0
  19. mock_proxy-0.1.0/server/hub/routers/skills.py +76 -0
  20. mock_proxy-0.1.0/server/hub/routers/targets.py +49 -0
  21. mock_proxy-0.1.0/server/hub/routers/traffic.py +39 -0
  22. mock_proxy-0.1.0/server/hub/ws_manager.py +45 -0
  23. mock_proxy-0.1.0/server/proxy/__init__.py +1 -0
  24. mock_proxy-0.1.0/server/proxy/engine.py +36 -0
  25. mock_proxy-0.1.0/server/proxy/executor.py +92 -0
  26. mock_proxy-0.1.0/server/proxy/matcher.py +78 -0
  27. mock_proxy-0.1.0/server/proxy/models.py +37 -0
  28. mock_proxy-0.1.0/server/proxy/passthrough.py +55 -0
  29. mock_proxy-0.1.0/server/static/assets/index-BD2Xncdq.css +1 -0
  30. mock_proxy-0.1.0/server/static/assets/index-Cf7QFgJv.js +119 -0
  31. mock_proxy-0.1.0/server/static/index.html +14 -0
  32. mock_proxy-0.1.0/skills/mock-proxy/SKILL.md +55 -0
@@ -0,0 +1,41 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ .venv/
8
+ env/
9
+ venv/
10
+ ENV/
11
+ *.egg-info/
12
+ dist/
13
+ build/
14
+ .pytest_cache/
15
+ .coverage
16
+ htmlcov/
17
+
18
+ # Node / Frontend
19
+ node_modules/
20
+ npm-debug.log*
21
+ yarn-debug.log*
22
+ yarn-error.log*
23
+ pnpm-debug.log*
24
+ .pnpm-debug.log*
25
+ web/dist/
26
+ web/.vite/
27
+
28
+ # Environment & local files
29
+ .env
30
+ .env.local
31
+ *.local
32
+ *.log
33
+ data/
34
+ *.db
35
+ *.sqlite3
36
+
37
+ # IDE / OS
38
+ .vscode/
39
+ .idea/
40
+ .DS_Store
41
+ *.swp
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.5
2
+ Name: mock-proxy
3
+ Version: 0.1.0
4
+ Summary: Mock Proxy Service with iptables interception and dynamic rules
5
+ Author: Developer
6
+ License: MIT
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: POSIX :: Linux
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Internet :: Proxy Servers
11
+ Classifier: Topic :: Software Development :: Testing :: Mocking
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: aiosqlite>=0.20.0
14
+ Requires-Dist: fastapi>=0.115.0
15
+ Requires-Dist: httpx>=0.27.0
16
+ Requires-Dist: pydantic>=2.8.0
17
+ Requires-Dist: uvicorn[standard]>=0.30.0
18
+ Requires-Dist: websockets>=12.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Mock Proxy Service (Mock 代理服务)
22
+
23
+ 一个用于在开发测试过程中拦截第三方/外部 HTTP 接口并模拟返回定制数据的全功能 Mock 代理服务。
24
+
25
+ 解决痛点:业务应用(如工程 A)调用了外部接口 `http://xxx.com/test`,代码中针对此接口报错编写了 catch 容错/降级逻辑,但在实际联调测试时外部接口极少报错。借助本服务,可以在**完全不改动业务代码、不污染业务网络配置**的前提下,通过客户端 Linux `iptables` 安全劫持与服务端规则引擎模拟 500、502、超时或异常响应,并提供现代化的 Web UI 监控看板与 AI Agent Skill 赋能。
26
+
27
+ ---
28
+
29
+ ## 🌟 核心特性
30
+
31
+ - **无侵入透明劫持**:客户端 Agent 自动在 Linux 本机 `iptables` 中创建隔离的 `MOCK_PROXY_OUTPUT` 专有链,精准 DNAT 劫持目标域名/IP 到服务端。
32
+ - **全生命周期安全保障 (Safe Teardown)**:
33
+ - 客户端严格向下兼容 **Python 3.6+**,便于在各类旧版服务器(CentOS 7、Ubuntu 18.04 等)上直接运行。
34
+ - 自动监听退出信号(Ctrl+C / SIGTERM / atexit),进程终止时**100% 自动还原与清理 iptables 规则**。
35
+ - 提供独立急救清理命令:`python -m client.agent clean`。
36
+ - **高级多维规则引擎与多服务分派**:
37
+ - 支持 Host 通配符(如 `*.partner.com`)、Path 精确/前缀/正则表达式(如 `^/api/v1/.*$`)、HTTP Method、Query 参数与 Body 匹配。
38
+ - **支持按服务/代理分派生效**:每条规则可指定生效的一个或多个具体服务(如 `order-service-dev`、`payment-uat` 或全局通用 `*`),避免多服务/多环境测试相互干扰。
39
+ - 支持**静态响应**(自定义状态码如 500/502/400、自定义响应头、响应延迟模拟、JSON/文本)。
40
+ - 支持**Python 沙箱动态脚本**(可模拟概率性抛错、基于请求参数动态计算响应内容)。
41
+
42
+ - **未命中真实透传 (Passthrough)**:未匹配任何 Mock 规则的请求原样转发至真实外部接口,返回真实响应。
43
+ - **现代化 Web UI 管理后台 (Vue 3 + Element Plus)**:
44
+ - **Mock 规则管理**:在线配置、规则开关、优先级调整、动态脚本语法高亮与测试。
45
+ - **拦截目标配置**:添加/移除需劫持的外部域名,变更实时推送给所有在线客户端 Agent。
46
+ - **实时抓包控制台 (Live Traffic Inspector)**:类似 Charles/Fiddler 的实时流量流,支持请求头/体对比,以及**“一键从流量生成 Mock 规则”**。
47
+ - **客户端节点状态**:实时观察在线 Agent 节点与心跳。
48
+ - **AI Agent Skill 深度集成**:内置 `skills/mock-proxy/SKILL.md`,可供 OpenCode、Claude 及其他 AI 辅助编程 Agent 直接调用,实现“自动配 Mock -> 触发测试 -> 审查抓包 -> 清理还原”的自动化闭环!
49
+
50
+ ---
51
+
52
+ ## 📂 项目目录结构
53
+
54
+ ```text
55
+ mock-proxy/
56
+ ├── server/ # 服务端核心
57
+ │ ├── app.py # FastAPI 主服务(集成 REST、WebSocket、静态托管)
58
+ │ ├── config.py # 服务端配置 (端口、数据目录)
59
+ │ ├── proxy/ # 代理引擎模块
60
+ │ │ ├── engine.py # 代理处理与生命周期分发
61
+ │ │ ├── matcher.py # 多维规则匹配引擎
62
+ │ │ ├── executor.py # 动态 Python 脚本沙箱执行器
63
+ │ │ └── passthrough.py # 真实上游透传客户端
64
+ │ ├── hub/ # 管控中心模块
65
+ │ │ ├── database.py # SQLite 异步存储
66
+ │ │ ├── ws_manager.py # WebSocket 广播管理 (Agent & Web UI)
67
+ │ │ └── routers/ # REST API 路由 (rules, targets, traffic, agents, skills)
68
+ │ └── static/ # Vue 3 前端生产构建产物
69
+ ├── client/ # 客户端 Agent (Python 3.6+ 严格向下兼容)
70
+ │ ├── agent.py # Agent 守护进程命令行入口
71
+ │ ├── iptables.py # Linux iptables 隔离链与安全清理
72
+ │ ├── dns_watcher.py # 动态 DNS 解析监控
73
+ │ └── ws_client.py # WebSocket 策略同步客户端
74
+ ├── skills/ # AI Agent Skill 技能定义
75
+ │ └── mock-proxy/
76
+ │ └── SKILL.md # 提供给 OpenCode / AI Agent 调用的技能说明与脚本
77
+ ├── web/ # Vue 3 + Element Plus 前端工程源码
78
+ └── tests/ # 自动化测试套件 (全量覆盖)
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 🚀 快速启动指南
84
+
85
+ ### 1. 启动 Mock Proxy 服务端
86
+
87
+ 依赖环境:Python 3.10+、uv(或 pip)
88
+
89
+ ```bash
90
+ # 进入项目目录
91
+ cd /home/coder/project/mock-proxy
92
+
93
+ # 使用 uv 同步安装依赖并启动服务
94
+ uv run uvicorn server.app:app --host 0.0.0.0 --port 8000
95
+ ```
96
+ - **Web UI 管理后台**:打开浏览器访问 `http://<服务器IP>:8000`
97
+ - **代理端口(Proxy Engine)**:监听 `0.0.0.0:8888`
98
+
99
+ ---
100
+
101
+ ### 2. 启动客户端 Agent(业务应用所在机器)
102
+
103
+ 依赖环境:Linux 宿主机、Python 3.6+(需 sudo 权限配置 iptables)
104
+
105
+ ```bash
106
+ # 启动常驻守护进程(连接服务端同步目标并管理 iptables,可通过 --service 标识所属业务服务/环境)
107
+ sudo python3 -m client.agent run --server <服务端IP> --hub-port 8000 --proxy-port 8888 --service order-service-dev
108
+
109
+ # 若在本地测试或无 root 权限环境演练,可附加 --dry-run 模式:
110
+ python3 -m client.agent run --server 127.0.0.1 --service order-service-dev --dry-run
111
+ ```
112
+
113
+
114
+ #### 急救清理与状态查看
115
+ 如果 Agent 意外被强杀导致网络拦截未解除,可随时执行独立急救命令:
116
+ ```bash
117
+ # 一键检测并安全清除所有 MOCK_PROXY_* iptables 规则链
118
+ sudo python3 -m client.agent clean
119
+
120
+ # 查看当前 iptables 生效规则
121
+ sudo python3 -m client.agent status
122
+ ```
123
+
124
+ ---
125
+
126
+ ## 🧪 典型测试场景演示:模拟第三方接口 500 异常
127
+
128
+ 假设工程 A 代码中有如下逻辑:
129
+ ```python
130
+ try:
131
+ resp = requests.get("http://xxx.com/test", timeout=3)
132
+ resp.raise_for_status()
133
+ data = resp.json()
134
+ except requests.exceptions.RequestException as e:
135
+ # 这是我们要重点验证的容错捕获逻辑
136
+ logger.error("第三方接口异常,执行本地降级策略")
137
+ return fallback_data()
138
+ ```
139
+
140
+ ### 验证步骤:
141
+ 1. **添加拦截目标**:
142
+ 在 Web 界面【拦截目标配置】中,添加域名 `xxx.com`(端口 80)。此时客户端 Agent 会自动将发往 `xxx.com:80` 的流量劫持转发到 Mock 服务端。
143
+ 2. **配置 Mock 规则**:
144
+ 在 Web 界面【Mock 规则管理】中新建规则:
145
+ - 目标 Host:`xxx.com`
146
+ - 请求路径:`/test`
147
+ - 状态码:`500`
148
+ - 响应体:`{"code": "ERR_REMOTE", "message": "Third party unavailable"}`
149
+ 3. **触发业务请求**:
150
+ 运行工程 A,工程 A 发起请求 `http://xxx.com/test`,直接收到 Mock 返回的 500 错误。
151
+ 4. **实时观测与验证**:
152
+ - 查看工程 A 控制台,确认 catch 异常处理逻辑被成功触发并执行降级!
153
+ - 打开 Web 界面【实时流量监控】,可清晰看到该次调用的时间、Method、URL、匹配的规则名称及返回结果。
154
+
155
+ ---
156
+
157
+ ## 🤖 AI Agent 调用说明 (OpenCode / LLM Skill)
158
+
159
+ 本系统原生支持被 AI Agent 动态调用。AI 智能体可读取 `skills/mock-proxy/SKILL.md`,或者通过服务端接口获取调用定义:
160
+
161
+ ```bash
162
+ # 获取技能说明文档
163
+ curl http://localhost:8000/api/agent-skills/SKILL.md
164
+
165
+ # 获取标准函数调用 Schema
166
+ curl http://localhost:8000/api/agent-skills/tools
167
+ ```
168
+
169
+ AI 智能体在自动排查或编写降级逻辑时,可以直接发起 HTTP 请求配置 Mock 规则进行自闭环测试验证。
170
+
171
+ ---
172
+
173
+ ## 🧪 运行自动化测试
174
+
175
+ ```bash
176
+ uv run pytest -v
177
+ ```
178
+ 所有 21 项测试(包含配置、代理转发、多维规则引擎、脚本沙箱、服务分派隔离、iptables 链构建、REST API 及端到端闭环)均全量通过。
179
+
180
+ 完整的 Web UI 操作步骤与浏览器测试记录见:[`docs/manual/web-ui-usage.md`](docs/manual/web-ui-usage.md)。
@@ -0,0 +1,160 @@
1
+ # Mock Proxy Service (Mock 代理服务)
2
+
3
+ 一个用于在开发测试过程中拦截第三方/外部 HTTP 接口并模拟返回定制数据的全功能 Mock 代理服务。
4
+
5
+ 解决痛点:业务应用(如工程 A)调用了外部接口 `http://xxx.com/test`,代码中针对此接口报错编写了 catch 容错/降级逻辑,但在实际联调测试时外部接口极少报错。借助本服务,可以在**完全不改动业务代码、不污染业务网络配置**的前提下,通过客户端 Linux `iptables` 安全劫持与服务端规则引擎模拟 500、502、超时或异常响应,并提供现代化的 Web UI 监控看板与 AI Agent Skill 赋能。
6
+
7
+ ---
8
+
9
+ ## 🌟 核心特性
10
+
11
+ - **无侵入透明劫持**:客户端 Agent 自动在 Linux 本机 `iptables` 中创建隔离的 `MOCK_PROXY_OUTPUT` 专有链,精准 DNAT 劫持目标域名/IP 到服务端。
12
+ - **全生命周期安全保障 (Safe Teardown)**:
13
+ - 客户端严格向下兼容 **Python 3.6+**,便于在各类旧版服务器(CentOS 7、Ubuntu 18.04 等)上直接运行。
14
+ - 自动监听退出信号(Ctrl+C / SIGTERM / atexit),进程终止时**100% 自动还原与清理 iptables 规则**。
15
+ - 提供独立急救清理命令:`python -m client.agent clean`。
16
+ - **高级多维规则引擎与多服务分派**:
17
+ - 支持 Host 通配符(如 `*.partner.com`)、Path 精确/前缀/正则表达式(如 `^/api/v1/.*$`)、HTTP Method、Query 参数与 Body 匹配。
18
+ - **支持按服务/代理分派生效**:每条规则可指定生效的一个或多个具体服务(如 `order-service-dev`、`payment-uat` 或全局通用 `*`),避免多服务/多环境测试相互干扰。
19
+ - 支持**静态响应**(自定义状态码如 500/502/400、自定义响应头、响应延迟模拟、JSON/文本)。
20
+ - 支持**Python 沙箱动态脚本**(可模拟概率性抛错、基于请求参数动态计算响应内容)。
21
+
22
+ - **未命中真实透传 (Passthrough)**:未匹配任何 Mock 规则的请求原样转发至真实外部接口,返回真实响应。
23
+ - **现代化 Web UI 管理后台 (Vue 3 + Element Plus)**:
24
+ - **Mock 规则管理**:在线配置、规则开关、优先级调整、动态脚本语法高亮与测试。
25
+ - **拦截目标配置**:添加/移除需劫持的外部域名,变更实时推送给所有在线客户端 Agent。
26
+ - **实时抓包控制台 (Live Traffic Inspector)**:类似 Charles/Fiddler 的实时流量流,支持请求头/体对比,以及**“一键从流量生成 Mock 规则”**。
27
+ - **客户端节点状态**:实时观察在线 Agent 节点与心跳。
28
+ - **AI Agent Skill 深度集成**:内置 `skills/mock-proxy/SKILL.md`,可供 OpenCode、Claude 及其他 AI 辅助编程 Agent 直接调用,实现“自动配 Mock -> 触发测试 -> 审查抓包 -> 清理还原”的自动化闭环!
29
+
30
+ ---
31
+
32
+ ## 📂 项目目录结构
33
+
34
+ ```text
35
+ mock-proxy/
36
+ ├── server/ # 服务端核心
37
+ │ ├── app.py # FastAPI 主服务(集成 REST、WebSocket、静态托管)
38
+ │ ├── config.py # 服务端配置 (端口、数据目录)
39
+ │ ├── proxy/ # 代理引擎模块
40
+ │ │ ├── engine.py # 代理处理与生命周期分发
41
+ │ │ ├── matcher.py # 多维规则匹配引擎
42
+ │ │ ├── executor.py # 动态 Python 脚本沙箱执行器
43
+ │ │ └── passthrough.py # 真实上游透传客户端
44
+ │ ├── hub/ # 管控中心模块
45
+ │ │ ├── database.py # SQLite 异步存储
46
+ │ │ ├── ws_manager.py # WebSocket 广播管理 (Agent & Web UI)
47
+ │ │ └── routers/ # REST API 路由 (rules, targets, traffic, agents, skills)
48
+ │ └── static/ # Vue 3 前端生产构建产物
49
+ ├── client/ # 客户端 Agent (Python 3.6+ 严格向下兼容)
50
+ │ ├── agent.py # Agent 守护进程命令行入口
51
+ │ ├── iptables.py # Linux iptables 隔离链与安全清理
52
+ │ ├── dns_watcher.py # 动态 DNS 解析监控
53
+ │ └── ws_client.py # WebSocket 策略同步客户端
54
+ ├── skills/ # AI Agent Skill 技能定义
55
+ │ └── mock-proxy/
56
+ │ └── SKILL.md # 提供给 OpenCode / AI Agent 调用的技能说明与脚本
57
+ ├── web/ # Vue 3 + Element Plus 前端工程源码
58
+ └── tests/ # 自动化测试套件 (全量覆盖)
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 🚀 快速启动指南
64
+
65
+ ### 1. 启动 Mock Proxy 服务端
66
+
67
+ 依赖环境:Python 3.10+、uv(或 pip)
68
+
69
+ ```bash
70
+ # 进入项目目录
71
+ cd /home/coder/project/mock-proxy
72
+
73
+ # 使用 uv 同步安装依赖并启动服务
74
+ uv run uvicorn server.app:app --host 0.0.0.0 --port 8000
75
+ ```
76
+ - **Web UI 管理后台**:打开浏览器访问 `http://<服务器IP>:8000`
77
+ - **代理端口(Proxy Engine)**:监听 `0.0.0.0:8888`
78
+
79
+ ---
80
+
81
+ ### 2. 启动客户端 Agent(业务应用所在机器)
82
+
83
+ 依赖环境:Linux 宿主机、Python 3.6+(需 sudo 权限配置 iptables)
84
+
85
+ ```bash
86
+ # 启动常驻守护进程(连接服务端同步目标并管理 iptables,可通过 --service 标识所属业务服务/环境)
87
+ sudo python3 -m client.agent run --server <服务端IP> --hub-port 8000 --proxy-port 8888 --service order-service-dev
88
+
89
+ # 若在本地测试或无 root 权限环境演练,可附加 --dry-run 模式:
90
+ python3 -m client.agent run --server 127.0.0.1 --service order-service-dev --dry-run
91
+ ```
92
+
93
+
94
+ #### 急救清理与状态查看
95
+ 如果 Agent 意外被强杀导致网络拦截未解除,可随时执行独立急救命令:
96
+ ```bash
97
+ # 一键检测并安全清除所有 MOCK_PROXY_* iptables 规则链
98
+ sudo python3 -m client.agent clean
99
+
100
+ # 查看当前 iptables 生效规则
101
+ sudo python3 -m client.agent status
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 🧪 典型测试场景演示:模拟第三方接口 500 异常
107
+
108
+ 假设工程 A 代码中有如下逻辑:
109
+ ```python
110
+ try:
111
+ resp = requests.get("http://xxx.com/test", timeout=3)
112
+ resp.raise_for_status()
113
+ data = resp.json()
114
+ except requests.exceptions.RequestException as e:
115
+ # 这是我们要重点验证的容错捕获逻辑
116
+ logger.error("第三方接口异常,执行本地降级策略")
117
+ return fallback_data()
118
+ ```
119
+
120
+ ### 验证步骤:
121
+ 1. **添加拦截目标**:
122
+ 在 Web 界面【拦截目标配置】中,添加域名 `xxx.com`(端口 80)。此时客户端 Agent 会自动将发往 `xxx.com:80` 的流量劫持转发到 Mock 服务端。
123
+ 2. **配置 Mock 规则**:
124
+ 在 Web 界面【Mock 规则管理】中新建规则:
125
+ - 目标 Host:`xxx.com`
126
+ - 请求路径:`/test`
127
+ - 状态码:`500`
128
+ - 响应体:`{"code": "ERR_REMOTE", "message": "Third party unavailable"}`
129
+ 3. **触发业务请求**:
130
+ 运行工程 A,工程 A 发起请求 `http://xxx.com/test`,直接收到 Mock 返回的 500 错误。
131
+ 4. **实时观测与验证**:
132
+ - 查看工程 A 控制台,确认 catch 异常处理逻辑被成功触发并执行降级!
133
+ - 打开 Web 界面【实时流量监控】,可清晰看到该次调用的时间、Method、URL、匹配的规则名称及返回结果。
134
+
135
+ ---
136
+
137
+ ## 🤖 AI Agent 调用说明 (OpenCode / LLM Skill)
138
+
139
+ 本系统原生支持被 AI Agent 动态调用。AI 智能体可读取 `skills/mock-proxy/SKILL.md`,或者通过服务端接口获取调用定义:
140
+
141
+ ```bash
142
+ # 获取技能说明文档
143
+ curl http://localhost:8000/api/agent-skills/SKILL.md
144
+
145
+ # 获取标准函数调用 Schema
146
+ curl http://localhost:8000/api/agent-skills/tools
147
+ ```
148
+
149
+ AI 智能体在自动排查或编写降级逻辑时,可以直接发起 HTTP 请求配置 Mock 规则进行自闭环测试验证。
150
+
151
+ ---
152
+
153
+ ## 🧪 运行自动化测试
154
+
155
+ ```bash
156
+ uv run pytest -v
157
+ ```
158
+ 所有 21 项测试(包含配置、代理转发、多维规则引擎、脚本沙箱、服务分派隔离、iptables 链构建、REST API 及端到端闭环)均全量通过。
159
+
160
+ 完整的 Web UI 操作步骤与浏览器测试记录见:[`docs/manual/web-ui-usage.md`](docs/manual/web-ui-usage.md)。
@@ -0,0 +1 @@
1
+ # client package (compatible with Python 3.6+)
@@ -0,0 +1,129 @@
1
+ import argparse
2
+ import asyncio
3
+ import atexit
4
+ import logging
5
+ import signal
6
+ import sys
7
+ import uuid
8
+ from typing import List, Dict, Any
9
+ from client.iptables import IptablesManager
10
+ from client.dns_watcher import DNSWatcher
11
+ from client.ws_client import AgentWSClient
12
+
13
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
14
+ logger = logging.getLogger("mock-proxy-agent")
15
+
16
+ def build_parser():
17
+ parser = argparse.ArgumentParser(description="Mock Proxy Client Agent (Python 3.6+ Compatible)")
18
+ subparsers = parser.add_subparsers(dest="command")
19
+
20
+ run_parser = subparsers.add_parser("run", help="Run agent daemon")
21
+ run_parser.add_argument("--server", default="127.0.0.1", help="Control Hub host IP")
22
+ run_parser.add_argument("--hub-port", type=int, default=8000, help="Control Hub port")
23
+ run_parser.add_argument("--proxy-port", type=int, default=8888, help="Proxy engine port")
24
+ run_parser.add_argument("--service", default="", help="Business service name identifier (e.g. order-service-dev)")
25
+ run_parser.add_argument("--dry-run", action="store_true", help="Simulate iptables operations")
26
+
27
+ clean_parser = subparsers.add_parser("clean", help="Emergency cleanup of iptables chains")
28
+ clean_parser.add_argument("--dry-run", action="store_true")
29
+
30
+ status_parser = subparsers.add_parser("status", help="Show current iptables rules")
31
+ return parser
32
+
33
+ def main():
34
+ parser = build_parser()
35
+ args = parser.parse_args()
36
+
37
+ cmd = args.command or "run"
38
+
39
+ if cmd == "clean":
40
+ mgr = IptablesManager(server_host="0.0.0.0", server_port=0, dry_run=args.dry_run)
41
+ print("Cleaning up MOCK_PROXY iptables rules...")
42
+ mgr.teardown()
43
+ print("Done.")
44
+ return
45
+
46
+ if cmd == "status":
47
+ mgr = IptablesManager(server_host="0.0.0.0", server_port=0)
48
+ print(mgr.get_status())
49
+ return
50
+
51
+ # run 模式
52
+ iptables_mgr = IptablesManager(
53
+ server_host=args.server,
54
+ server_port=args.proxy_port,
55
+ dry_run=args.dry_run
56
+ )
57
+
58
+ cleaned = False
59
+
60
+ def cleanup():
61
+ nonlocal cleaned
62
+ if not cleaned:
63
+ cleaned = True
64
+ logger.info("Cleaning up iptables rules before exiting...")
65
+ iptables_mgr.teardown()
66
+
67
+ atexit.register(cleanup)
68
+
69
+ def handle_signal(sig, frame):
70
+ logger.info(f"Received signal {sig}, exiting gracefully...")
71
+ cleanup()
72
+ sys.exit(0)
73
+
74
+ signal.signal(signal.SIGINT, handle_signal)
75
+ signal.signal(signal.SIGTERM, handle_signal)
76
+
77
+ logger.info("Setting up iptables MOCK_PROXY_OUTPUT chain...")
78
+ iptables_mgr.setup()
79
+
80
+ current_targets: List[Dict[str, Any]] = []
81
+
82
+ def on_sync_targets(targets: List[Dict[str, Any]]):
83
+ nonlocal current_targets
84
+ current_targets = targets
85
+ logger.info(f"Received updated targets: {len(targets)} targets")
86
+ for t in targets:
87
+ if t.get("enabled", True):
88
+ ip = t.get("ip")
89
+ port = t.get("port", 80)
90
+ if ip:
91
+ iptables_mgr.add_target(ip, port)
92
+
93
+ def on_ips_changed(domain, added, removed):
94
+ logger.info(f"Domain {domain} IP update: added={added}, removed={removed}")
95
+ for ip in added:
96
+ iptables_mgr.add_target(ip, 80)
97
+ for ip in removed:
98
+ iptables_mgr.remove_target(ip, 80)
99
+
100
+ agent_id = f"agent_{uuid.uuid4().hex[:8]}"
101
+ ws_url = f"ws://{args.server}:{args.hub_port}/ws/agent"
102
+ service_name = args.service or socket.gethostname()
103
+ ws_client = AgentWSClient(ws_url, agent_id, on_sync_targets, service_name=service_name)
104
+ dns_watcher = DNSWatcher(check_interval=30.0)
105
+
106
+
107
+ async def run_loop():
108
+ # Python 3.6+ 兼容异步任务创建
109
+ watcher_task = asyncio.ensure_future(dns_watcher.watch(
110
+ get_target_domains=lambda: [t["domain"] for t in current_targets if t.get("domain")],
111
+ on_ips_changed=on_ips_changed
112
+ ))
113
+ await ws_client.start()
114
+ watcher_task.cancel()
115
+
116
+ try:
117
+ try:
118
+ loop = asyncio.get_event_loop()
119
+ except RuntimeError:
120
+ loop = asyncio.new_event_loop()
121
+ asyncio.set_event_loop(loop)
122
+ loop.run_until_complete(run_loop())
123
+ except KeyboardInterrupt:
124
+ pass
125
+ finally:
126
+ cleanup()
127
+
128
+ if __name__ == "__main__":
129
+ main()
@@ -0,0 +1,47 @@
1
+ import asyncio
2
+ import socket
3
+ import logging
4
+ from typing import Set, Callable, Dict, List
5
+
6
+ logger = logging.getLogger("dns-watcher")
7
+
8
+ class DNSWatcher:
9
+ def __init__(self, check_interval: float = 30.0):
10
+ self.check_interval = check_interval
11
+ self.domain_ip_cache: Dict[str, Set[str]] = {}
12
+ self._running = False
13
+
14
+ async def resolve_domain(self, domain: str) -> Set[str]:
15
+ try:
16
+ loop = asyncio.get_event_loop()
17
+ except RuntimeError:
18
+ loop = asyncio.new_event_loop()
19
+ asyncio.set_event_loop(loop)
20
+ try:
21
+ addrinfo = await loop.getaddrinfo(domain, None, family=socket.AF_INET)
22
+ ips = {info[4][0] for info in addrinfo}
23
+ return ips
24
+ except Exception as e:
25
+ logger.warning(f"DNS resolve error for {domain}: {e}")
26
+ return set()
27
+
28
+ async def watch(
29
+ self,
30
+ get_target_domains: Callable[[], List[str]],
31
+ on_ips_changed: Callable[[str, Set[str], Set[str]], None]
32
+ ):
33
+ self._running = True
34
+ while self._running:
35
+ domains = get_target_domains()
36
+ for domain in domains:
37
+ current_ips = await self.resolve_domain(domain)
38
+ old_ips = self.domain_ip_cache.get(domain, set())
39
+ added = current_ips - old_ips
40
+ removed = old_ips - current_ips
41
+ if added or removed:
42
+ self.domain_ip_cache[domain] = current_ips
43
+ on_ips_changed(domain, added, removed)
44
+ await asyncio.sleep(self.check_interval)
45
+
46
+ def stop(self):
47
+ self._running = False
@@ -0,0 +1,94 @@
1
+ import subprocess
2
+ import logging
3
+ from typing import List, Tuple, Set
4
+
5
+ logger = logging.getLogger("iptables-manager")
6
+
7
+ CHAIN_NAME = "MOCK_PROXY_OUTPUT"
8
+
9
+ class IptablesManager:
10
+ def __init__(self, server_host: str, server_port: int, dry_run: bool = False):
11
+ self.server_host = server_host
12
+ self.server_port = server_port
13
+ self.dry_run = dry_run
14
+ self.active_targets: Set[Tuple[str, int]] = set()
15
+
16
+ def _exec(self, cmd: str) -> bool:
17
+ if self.dry_run:
18
+ logger.info(f"[DRY-RUN] {cmd}")
19
+ return True
20
+ try:
21
+ # 使用 Python 3.6 兼容的 subprocess 参数
22
+ res = subprocess.run(
23
+ cmd,
24
+ shell=True,
25
+ check=True,
26
+ stdout=subprocess.PIPE,
27
+ stderr=subprocess.PIPE,
28
+ universal_newlines=True
29
+ )
30
+ return True
31
+ except subprocess.CalledProcessError as e:
32
+ err_msg = e.stderr.strip() if e.stderr else ""
33
+ logger.warning(f"iptables exec failed: {cmd}, err: {err_msg}")
34
+ return False
35
+
36
+ def setup(self) -> List[str]:
37
+ cmds = [
38
+ # 1. 若链不存在则创建专用链
39
+ f"iptables -t nat -N {CHAIN_NAME} 2>/dev/null || true",
40
+ # 2. 清空链内可能残留的旧规则
41
+ f"iptables -t nat -F {CHAIN_NAME}",
42
+ # 3. 防环规则:对与服务端本身通信流量放行 ACCEPT
43
+ f"iptables -t nat -A {CHAIN_NAME} -d {self.server_host} -p tcp --dport {self.server_port} -j ACCEPT",
44
+ # 4. 挂载到 OUTPUT 链(确保不重复添加)
45
+ f"iptables -t nat -C OUTPUT -p tcp -j {CHAIN_NAME} 2>/dev/null || iptables -t nat -A OUTPUT -p tcp -j {CHAIN_NAME}",
46
+ ]
47
+ for cmd in cmds:
48
+ self._exec(cmd)
49
+ return cmds
50
+
51
+ def add_target(self, target_ip: str, target_port: int = 80) -> List[str]:
52
+ if (target_ip, target_port) in self.active_targets:
53
+ return []
54
+ cmd = f"iptables -t nat -A {CHAIN_NAME} -p tcp -d {target_ip} --dport {target_port} -j DNAT --to-destination {self.server_host}:{self.server_port}"
55
+ self._exec(cmd)
56
+ self.active_targets.add((target_ip, target_port))
57
+ return [cmd]
58
+
59
+ def remove_target(self, target_ip: str, target_port: int = 80) -> List[str]:
60
+ if (target_ip, target_port) not in self.active_targets:
61
+ return []
62
+ cmd = f"iptables -t nat -D {CHAIN_NAME} -p tcp -d {target_ip} --dport {target_port} -j DNAT --to-destination {self.server_host}:{self.server_port} 2>/dev/null || true"
63
+ self._exec(cmd)
64
+ self.active_targets.discard((target_ip, target_port))
65
+ return [cmd]
66
+
67
+ def teardown(self) -> List[str]:
68
+ cmds = [
69
+ # 1. 从 OUTPUT 解除挂载
70
+ f"iptables -t nat -D OUTPUT -p tcp -j {CHAIN_NAME} 2>/dev/null || true",
71
+ # 2. 清空专用链
72
+ f"iptables -t nat -F {CHAIN_NAME} 2>/dev/null || true",
73
+ # 3. 删除专用链
74
+ f"iptables -t nat -X {CHAIN_NAME} 2>/dev/null || true",
75
+ ]
76
+ for cmd in cmds:
77
+ self._exec(cmd)
78
+ self.active_targets.clear()
79
+ return cmds
80
+
81
+ def get_status(self) -> str:
82
+ if self.dry_run:
83
+ return f"[DRY-RUN] Active targets: {list(self.active_targets)}"
84
+ try:
85
+ res = subprocess.run(
86
+ f"iptables -t nat -L {CHAIN_NAME} -n -v",
87
+ shell=True,
88
+ stdout=subprocess.PIPE,
89
+ stderr=subprocess.PIPE,
90
+ universal_newlines=True
91
+ )
92
+ return res.stdout
93
+ except Exception as e:
94
+ return f"Error reading iptables: {e}"