procproxy 0.1.1__tar.gz → 0.1.2__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.
- {procproxy-0.1.1/src/procproxy.egg-info → procproxy-0.1.2}/PKG-INFO +144 -14
- {procproxy-0.1.1 → procproxy-0.1.2}/README.md +143 -13
- {procproxy-0.1.1 → procproxy-0.1.2}/pyproject.toml +1 -1
- {procproxy-0.1.1 → procproxy-0.1.2/src/procproxy.egg-info}/PKG-INFO +144 -14
- {procproxy-0.1.1 → procproxy-0.1.2}/src/procproxy.egg-info/SOURCES.txt +1 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/__init__.py +1 -1
- procproxy-0.1.2/src/proxycore/api.py +315 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/canproxy.py +22 -20
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/checkroute.py +14 -10
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/cli.py +56 -72
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/config.py +28 -6
- procproxy-0.1.2/src/proxycore/config_source.py +154 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/service/__init__.py +11 -2
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/service/__main__.py +19 -5
- procproxy-0.1.1/src/proxycore/api.py +0 -226
- {procproxy-0.1.1 → procproxy-0.1.2}/LICENSE +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/setup.cfg +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/procproxy.egg-info/dependency_links.txt +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/procproxy.egg-info/entry_points.txt +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/procproxy.egg-info/requires.txt +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/procproxy.egg-info/top_level.txt +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/__main__.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/backend/__init__.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/backend/base.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/backend/null.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/backend/windivert/__init__.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/backend/windivert/filter.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/backend/windivert/pidmap.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/backend/windivert/pkt.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/data/default.yaml +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/dns/__init__.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/dns/cache.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/dns/modes.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/dns/udp.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/forward.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/ipc.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/log.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/pathsetup.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/probe.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/proc.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/rules.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/runtime.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/tunnel/__init__.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/tunnel/relay.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/tunnel/sessions.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/tunnel/sink.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/types.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/upstream/__init__.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/upstream/base.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/upstream/chain.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/upstream/http_connect.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/upstream/socks4.py +0 -0
- {procproxy-0.1.1 → procproxy-0.1.2}/src/proxycore/upstream/socks5.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: procproxy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: ProcProxy —— 进程级代理:按进程决定哪条连接走哪个上游代理
|
|
5
5
|
Author: Longersking
|
|
6
6
|
License-Expression: MIT
|
|
@@ -78,6 +78,8 @@ procproxy forward WeChat.exe --port 8848 # 真实转发(需管理员)
|
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
安装后直接运行 `procproxy` 会显示首次使用指引,不依赖任何样例文件。
|
|
81
|
+
Python SDK 也一样:`start_proxy()` 不传配置即可启动(内置 `backend=null`),
|
|
82
|
+
不需要先跑 `init`;`init` 只用于生成可编辑配置。
|
|
81
83
|
|
|
82
84
|
非管理员:WinDivert 抓包必须管理员;可以用 `--backend null` 做转发链路联调,
|
|
83
85
|
或在服务已启动、且配置了 `service.ipc_allow_group` 时读取状态。
|
|
@@ -344,24 +346,151 @@ rules: # 自上而下,**首个命中生
|
|
|
344
346
|
|
|
345
347
|
## 编程接口
|
|
346
348
|
|
|
349
|
+
`proxycore.api` 是与 CLI 共用同一套配置、规则引擎和会话表的 SDK。
|
|
350
|
+
完整教程见 [`docs/15-编程接口与SDK.md`](docs/15-编程接口与SDK.md)。
|
|
351
|
+
|
|
352
|
+
### 30 秒:零配置启动
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from proxycore.api import start_proxy
|
|
356
|
+
|
|
357
|
+
with start_proxy() as ppx: # 不需要任何配置文件
|
|
358
|
+
print(ppx.config_source) # "packaged"
|
|
359
|
+
print(ppx.config_path) # .../proxycore/data/default.yaml
|
|
360
|
+
print(ppx.status()) # 后端 / 规则 / 会话 / UDP / 配置来源
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
不传配置时的来源优先级:
|
|
364
|
+
|
|
365
|
+
| 优先级 | 来源 | 说明 |
|
|
366
|
+
|---|---|---|
|
|
367
|
+
| 1 | `start_proxy(path)` | 显式路径(也接受内存中的 `Config` 对象) |
|
|
368
|
+
| 2 | `PROCPROXY_CONFIG` | 指向不存在文件会直接报错,不回退 |
|
|
369
|
+
| 3 | `~/.procproxy/config.yaml` | `procproxy init` 生成的用户配置 |
|
|
370
|
+
| 4 | 包内 `data/default.yaml` | `backend=null`,不抓包、不改道 |
|
|
371
|
+
|
|
372
|
+
内置配置**不会**监听 sink,也**不会**创建任何文件。它适合装配、规则决策、
|
|
373
|
+
状态查询和联调;要真实抓包请用 `backend="windivert"`(需管理员)或
|
|
374
|
+
`procproxy init` 生成自己的配置。
|
|
375
|
+
|
|
376
|
+
### 显式配置与测试转发
|
|
377
|
+
|
|
347
378
|
```python
|
|
348
|
-
from proxycore import
|
|
379
|
+
from proxycore.api import start_proxy
|
|
349
380
|
|
|
350
|
-
#
|
|
351
|
-
with
|
|
352
|
-
print(
|
|
381
|
+
# 显式路径 + 真实抓包(需要管理员)
|
|
382
|
+
with start_proxy(r"D:\cfg\procproxy.yaml", backend="windivert") as ppx:
|
|
383
|
+
print(ppx.sink) # sink 监听地址
|
|
384
|
+
print(ppx.sessions(limit=20))
|
|
353
385
|
|
|
354
|
-
#
|
|
355
|
-
with
|
|
356
|
-
|
|
386
|
+
# 联调模式:backend=null + 把 sink 收到的连接固定转发到目标
|
|
387
|
+
with start_proxy(backend="null", sink_test_target="127.0.0.1:8080",
|
|
388
|
+
upstream="local-mock") as ppx:
|
|
357
389
|
...
|
|
390
|
+
# curl -x http://127.0.0.1:19080 http://any/ → 127.0.0.1:8080
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### 在代码里构建配置
|
|
394
|
+
|
|
395
|
+
可以从字典构造 `Config`,也可以先落盘再按路径加载:
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
from proxycore.config import Config, atomic_write
|
|
399
|
+
from proxycore.api import start_proxy
|
|
400
|
+
|
|
401
|
+
doc = {
|
|
402
|
+
"version": 1,
|
|
403
|
+
"service": {"sink": {"host": "127.0.0.1", "port": 19080}, "backend": "null"},
|
|
404
|
+
"profile": {
|
|
405
|
+
"name": "myapp",
|
|
406
|
+
"default_action": "direct",
|
|
407
|
+
"dns_mode": "remote",
|
|
408
|
+
"rules": [
|
|
409
|
+
{"id": "r1", "action": "proxy", "upstream": "office",
|
|
410
|
+
"targets": "chrome.exe;msedge.exe", "dst_port": "443,80"},
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
"upstreams": [
|
|
414
|
+
{"alias": "office", "kind": "socks5", "host": "127.0.0.1", "port": 1080},
|
|
415
|
+
],
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
cfg = Config.from_dict(doc)
|
|
419
|
+
cfg.validate() # from_dict 只解析,不校验
|
|
420
|
+
atomic_write(cfg, "myconfig.yaml") # 临时文件 → 校验 → 原子替换
|
|
421
|
+
|
|
422
|
+
with start_proxy("myconfig.yaml") as ppx:
|
|
423
|
+
...
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
> `Config.from_dict()` 不会解析 `password_ref`;需要凭据时请用
|
|
427
|
+
> `Config.load()` / `start_proxy(path)`,或自己给 `upstream.password` 赋值。
|
|
358
428
|
|
|
359
|
-
|
|
360
|
-
|
|
429
|
+
### 只做决策,不启动数据面
|
|
430
|
+
|
|
431
|
+
```python
|
|
432
|
+
from proxycore.api import make_runtime
|
|
433
|
+
|
|
434
|
+
runtime = make_runtime() # 默认用内置配置,backend=null
|
|
361
435
|
decision = runtime.decide(conn_request)
|
|
436
|
+
print(decision.action, decision.reason)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### 生命周期与句柄
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
ppx = start_proxy()
|
|
443
|
+
try:
|
|
444
|
+
print(ppx.status())
|
|
445
|
+
finally:
|
|
446
|
+
ppx.stop() # 幂等;with 退出时也会自动调用
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
| 成员 | 说明 |
|
|
450
|
+
|---|---|
|
|
451
|
+
| `status()` | 后端、规则、会话、熔断、DNS、UDP、sink、`config_path`、`config_source` |
|
|
452
|
+
| `sessions(limit=50)` / `active_sessions()` | 会话明细 / 活跃数 |
|
|
453
|
+
| `reload(path="")` | 热重载;不传路径则重读当前文件。`sink.port`/`backend` 变更需重启 |
|
|
454
|
+
| `decide(conn)` | 只问规则引擎,不抓包、不改道 |
|
|
455
|
+
| `profile` | 当前生效的 `Profile` |
|
|
456
|
+
| `config_path` / `config_source` | 当前配置的绝对路径与来源 |
|
|
457
|
+
| `running` | 是否仍在运行 |
|
|
458
|
+
|
|
459
|
+
`start_proxy()` 会启动后台维护线程;`run_forever()` 会阻塞到 Ctrl+C。
|
|
460
|
+
请在主线程使用,并在退出时 `stop()`(或使用 `with`)。
|
|
461
|
+
|
|
462
|
+
### 错误处理
|
|
463
|
+
|
|
464
|
+
配置 / 后端 / 启动失败统一抛 `ProxyStartError`,`code` 是稳定的机器可读标识:
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
from proxycore.api import ProxyStartError, start_proxy
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
start_proxy("missing.yaml")
|
|
471
|
+
except ProxyStartError as exc:
|
|
472
|
+
print(exc.code) # config_not_found
|
|
362
473
|
```
|
|
363
474
|
|
|
364
|
-
|
|
475
|
+
| code | 含义 |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `config_not_found` | 配置文件不存在(显式路径 / 环境变量不会回退) |
|
|
478
|
+
| `config_unreadable` | 文件存在但读不了(权限、目录等) |
|
|
479
|
+
| `config_parse_invalid` | YAML 语法错误 / 根节点非映射 / 非 UTF-8 |
|
|
480
|
+
| `config_version_unsupported` | `version != 1` |
|
|
481
|
+
| `rule_upstream_missing` 等 | 规则或上游校验失败(完整列表见文档) |
|
|
482
|
+
| `E_BACKEND_UNAVAILABLE` | 后端不可用(未知后端、WinDivert 不可加载等) |
|
|
483
|
+
|
|
484
|
+
`api.load_config()` 同样抛 `ProxyStartError`,不是 `ConfigError`。
|
|
485
|
+
|
|
486
|
+
### 环境变量
|
|
487
|
+
|
|
488
|
+
| 变量 | 作用 |
|
|
489
|
+
|---|---|
|
|
490
|
+
| `PROCPROXY_CONFIG` | 配置文件路径;显式路径之后、用户配置之前生效 |
|
|
491
|
+
| `PROCPROXY_RUNTIME_DIR` | 运行时状态/日志目录(CLI 与服务使用) |
|
|
492
|
+
|
|
493
|
+
跑 `python tests/tools/api_demo.py` 看完整演示。
|
|
365
494
|
|
|
366
495
|
---
|
|
367
496
|
|
|
@@ -406,12 +535,13 @@ src/proxycore/ 核心包
|
|
|
406
535
|
├── tunnel/ sink + relay(本地落地与双向转发)
|
|
407
536
|
├── upstream/ SOCKS5 / SOCKS4 / HTTP CONNECT / 跳链
|
|
408
537
|
├── dns/ DNS 三模式与 UDP
|
|
538
|
+
├── config_source.py 配置来源解析(显式 / env / 用户 / 样例 / 内置)
|
|
409
539
|
├── forward.py ← procproxy.py <进程名> 的实现
|
|
410
540
|
├── canproxy.py / checkroute.py / probe.py / pathsetup.py ← 其余子命令
|
|
411
541
|
└── service/ ipc.py Windows 服务 + 命名管道
|
|
412
542
|
samples/ mock 服务 + 示例配置
|
|
413
|
-
docs/ 文档(00
|
|
414
|
-
tests/ 测试套件(
|
|
543
|
+
docs/ 文档(00 架构 / 07 规则 / 11 排错 / 14 抓包 / 15 SDK)
|
|
544
|
+
tests/ 测试套件(619 passed / 4 skipped)
|
|
415
545
|
└── tools/ 验证脚本(run_demo / run_e2e / bench / …)
|
|
416
546
|
```
|
|
417
547
|
|
|
@@ -442,7 +572,7 @@ tests/ 测试套件(555 passed)
|
|
|
442
572
|
|
|
443
573
|
```bash
|
|
444
574
|
pip install -e ".[dev]"
|
|
445
|
-
python -m pytest -q tests/ #
|
|
575
|
+
python -m pytest -q tests/ # 619 passed / 4 skipped(非管理员 Windows)
|
|
446
576
|
python -m ruff check src tests
|
|
447
577
|
python -m mypy src/proxycore
|
|
448
578
|
```
|
|
@@ -39,6 +39,8 @@ procproxy forward WeChat.exe --port 8848 # 真实转发(需管理员)
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
安装后直接运行 `procproxy` 会显示首次使用指引,不依赖任何样例文件。
|
|
42
|
+
Python SDK 也一样:`start_proxy()` 不传配置即可启动(内置 `backend=null`),
|
|
43
|
+
不需要先跑 `init`;`init` 只用于生成可编辑配置。
|
|
42
44
|
|
|
43
45
|
非管理员:WinDivert 抓包必须管理员;可以用 `--backend null` 做转发链路联调,
|
|
44
46
|
或在服务已启动、且配置了 `service.ipc_allow_group` 时读取状态。
|
|
@@ -305,24 +307,151 @@ rules: # 自上而下,**首个命中生
|
|
|
305
307
|
|
|
306
308
|
## 编程接口
|
|
307
309
|
|
|
310
|
+
`proxycore.api` 是与 CLI 共用同一套配置、规则引擎和会话表的 SDK。
|
|
311
|
+
完整教程见 [`docs/15-编程接口与SDK.md`](docs/15-编程接口与SDK.md)。
|
|
312
|
+
|
|
313
|
+
### 30 秒:零配置启动
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from proxycore.api import start_proxy
|
|
317
|
+
|
|
318
|
+
with start_proxy() as ppx: # 不需要任何配置文件
|
|
319
|
+
print(ppx.config_source) # "packaged"
|
|
320
|
+
print(ppx.config_path) # .../proxycore/data/default.yaml
|
|
321
|
+
print(ppx.status()) # 后端 / 规则 / 会话 / UDP / 配置来源
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
不传配置时的来源优先级:
|
|
325
|
+
|
|
326
|
+
| 优先级 | 来源 | 说明 |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| 1 | `start_proxy(path)` | 显式路径(也接受内存中的 `Config` 对象) |
|
|
329
|
+
| 2 | `PROCPROXY_CONFIG` | 指向不存在文件会直接报错,不回退 |
|
|
330
|
+
| 3 | `~/.procproxy/config.yaml` | `procproxy init` 生成的用户配置 |
|
|
331
|
+
| 4 | 包内 `data/default.yaml` | `backend=null`,不抓包、不改道 |
|
|
332
|
+
|
|
333
|
+
内置配置**不会**监听 sink,也**不会**创建任何文件。它适合装配、规则决策、
|
|
334
|
+
状态查询和联调;要真实抓包请用 `backend="windivert"`(需管理员)或
|
|
335
|
+
`procproxy init` 生成自己的配置。
|
|
336
|
+
|
|
337
|
+
### 显式配置与测试转发
|
|
338
|
+
|
|
308
339
|
```python
|
|
309
|
-
from proxycore import
|
|
340
|
+
from proxycore.api import start_proxy
|
|
310
341
|
|
|
311
|
-
#
|
|
312
|
-
with
|
|
313
|
-
print(
|
|
342
|
+
# 显式路径 + 真实抓包(需要管理员)
|
|
343
|
+
with start_proxy(r"D:\cfg\procproxy.yaml", backend="windivert") as ppx:
|
|
344
|
+
print(ppx.sink) # sink 监听地址
|
|
345
|
+
print(ppx.sessions(limit=20))
|
|
314
346
|
|
|
315
|
-
#
|
|
316
|
-
with
|
|
317
|
-
|
|
347
|
+
# 联调模式:backend=null + 把 sink 收到的连接固定转发到目标
|
|
348
|
+
with start_proxy(backend="null", sink_test_target="127.0.0.1:8080",
|
|
349
|
+
upstream="local-mock") as ppx:
|
|
318
350
|
...
|
|
351
|
+
# curl -x http://127.0.0.1:19080 http://any/ → 127.0.0.1:8080
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### 在代码里构建配置
|
|
355
|
+
|
|
356
|
+
可以从字典构造 `Config`,也可以先落盘再按路径加载:
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
from proxycore.config import Config, atomic_write
|
|
360
|
+
from proxycore.api import start_proxy
|
|
361
|
+
|
|
362
|
+
doc = {
|
|
363
|
+
"version": 1,
|
|
364
|
+
"service": {"sink": {"host": "127.0.0.1", "port": 19080}, "backend": "null"},
|
|
365
|
+
"profile": {
|
|
366
|
+
"name": "myapp",
|
|
367
|
+
"default_action": "direct",
|
|
368
|
+
"dns_mode": "remote",
|
|
369
|
+
"rules": [
|
|
370
|
+
{"id": "r1", "action": "proxy", "upstream": "office",
|
|
371
|
+
"targets": "chrome.exe;msedge.exe", "dst_port": "443,80"},
|
|
372
|
+
],
|
|
373
|
+
},
|
|
374
|
+
"upstreams": [
|
|
375
|
+
{"alias": "office", "kind": "socks5", "host": "127.0.0.1", "port": 1080},
|
|
376
|
+
],
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
cfg = Config.from_dict(doc)
|
|
380
|
+
cfg.validate() # from_dict 只解析,不校验
|
|
381
|
+
atomic_write(cfg, "myconfig.yaml") # 临时文件 → 校验 → 原子替换
|
|
382
|
+
|
|
383
|
+
with start_proxy("myconfig.yaml") as ppx:
|
|
384
|
+
...
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
> `Config.from_dict()` 不会解析 `password_ref`;需要凭据时请用
|
|
388
|
+
> `Config.load()` / `start_proxy(path)`,或自己给 `upstream.password` 赋值。
|
|
319
389
|
|
|
320
|
-
|
|
321
|
-
|
|
390
|
+
### 只做决策,不启动数据面
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
from proxycore.api import make_runtime
|
|
394
|
+
|
|
395
|
+
runtime = make_runtime() # 默认用内置配置,backend=null
|
|
322
396
|
decision = runtime.decide(conn_request)
|
|
397
|
+
print(decision.action, decision.reason)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 生命周期与句柄
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
ppx = start_proxy()
|
|
404
|
+
try:
|
|
405
|
+
print(ppx.status())
|
|
406
|
+
finally:
|
|
407
|
+
ppx.stop() # 幂等;with 退出时也会自动调用
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
| 成员 | 说明 |
|
|
411
|
+
|---|---|
|
|
412
|
+
| `status()` | 后端、规则、会话、熔断、DNS、UDP、sink、`config_path`、`config_source` |
|
|
413
|
+
| `sessions(limit=50)` / `active_sessions()` | 会话明细 / 活跃数 |
|
|
414
|
+
| `reload(path="")` | 热重载;不传路径则重读当前文件。`sink.port`/`backend` 变更需重启 |
|
|
415
|
+
| `decide(conn)` | 只问规则引擎,不抓包、不改道 |
|
|
416
|
+
| `profile` | 当前生效的 `Profile` |
|
|
417
|
+
| `config_path` / `config_source` | 当前配置的绝对路径与来源 |
|
|
418
|
+
| `running` | 是否仍在运行 |
|
|
419
|
+
|
|
420
|
+
`start_proxy()` 会启动后台维护线程;`run_forever()` 会阻塞到 Ctrl+C。
|
|
421
|
+
请在主线程使用,并在退出时 `stop()`(或使用 `with`)。
|
|
422
|
+
|
|
423
|
+
### 错误处理
|
|
424
|
+
|
|
425
|
+
配置 / 后端 / 启动失败统一抛 `ProxyStartError`,`code` 是稳定的机器可读标识:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
from proxycore.api import ProxyStartError, start_proxy
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
start_proxy("missing.yaml")
|
|
432
|
+
except ProxyStartError as exc:
|
|
433
|
+
print(exc.code) # config_not_found
|
|
323
434
|
```
|
|
324
435
|
|
|
325
|
-
|
|
436
|
+
| code | 含义 |
|
|
437
|
+
|---|---|
|
|
438
|
+
| `config_not_found` | 配置文件不存在(显式路径 / 环境变量不会回退) |
|
|
439
|
+
| `config_unreadable` | 文件存在但读不了(权限、目录等) |
|
|
440
|
+
| `config_parse_invalid` | YAML 语法错误 / 根节点非映射 / 非 UTF-8 |
|
|
441
|
+
| `config_version_unsupported` | `version != 1` |
|
|
442
|
+
| `rule_upstream_missing` 等 | 规则或上游校验失败(完整列表见文档) |
|
|
443
|
+
| `E_BACKEND_UNAVAILABLE` | 后端不可用(未知后端、WinDivert 不可加载等) |
|
|
444
|
+
|
|
445
|
+
`api.load_config()` 同样抛 `ProxyStartError`,不是 `ConfigError`。
|
|
446
|
+
|
|
447
|
+
### 环境变量
|
|
448
|
+
|
|
449
|
+
| 变量 | 作用 |
|
|
450
|
+
|---|---|
|
|
451
|
+
| `PROCPROXY_CONFIG` | 配置文件路径;显式路径之后、用户配置之前生效 |
|
|
452
|
+
| `PROCPROXY_RUNTIME_DIR` | 运行时状态/日志目录(CLI 与服务使用) |
|
|
453
|
+
|
|
454
|
+
跑 `python tests/tools/api_demo.py` 看完整演示。
|
|
326
455
|
|
|
327
456
|
---
|
|
328
457
|
|
|
@@ -367,12 +496,13 @@ src/proxycore/ 核心包
|
|
|
367
496
|
├── tunnel/ sink + relay(本地落地与双向转发)
|
|
368
497
|
├── upstream/ SOCKS5 / SOCKS4 / HTTP CONNECT / 跳链
|
|
369
498
|
├── dns/ DNS 三模式与 UDP
|
|
499
|
+
├── config_source.py 配置来源解析(显式 / env / 用户 / 样例 / 内置)
|
|
370
500
|
├── forward.py ← procproxy.py <进程名> 的实现
|
|
371
501
|
├── canproxy.py / checkroute.py / probe.py / pathsetup.py ← 其余子命令
|
|
372
502
|
└── service/ ipc.py Windows 服务 + 命名管道
|
|
373
503
|
samples/ mock 服务 + 示例配置
|
|
374
|
-
docs/ 文档(00
|
|
375
|
-
tests/ 测试套件(
|
|
504
|
+
docs/ 文档(00 架构 / 07 规则 / 11 排错 / 14 抓包 / 15 SDK)
|
|
505
|
+
tests/ 测试套件(619 passed / 4 skipped)
|
|
376
506
|
└── tools/ 验证脚本(run_demo / run_e2e / bench / …)
|
|
377
507
|
```
|
|
378
508
|
|
|
@@ -403,7 +533,7 @@ tests/ 测试套件(555 passed)
|
|
|
403
533
|
|
|
404
534
|
```bash
|
|
405
535
|
pip install -e ".[dev]"
|
|
406
|
-
python -m pytest -q tests/ #
|
|
536
|
+
python -m pytest -q tests/ # 619 passed / 4 skipped(非管理员 Windows)
|
|
407
537
|
python -m ruff check src tests
|
|
408
538
|
python -m mypy src/proxycore
|
|
409
539
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: procproxy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: ProcProxy —— 进程级代理:按进程决定哪条连接走哪个上游代理
|
|
5
5
|
Author: Longersking
|
|
6
6
|
License-Expression: MIT
|
|
@@ -78,6 +78,8 @@ procproxy forward WeChat.exe --port 8848 # 真实转发(需管理员)
|
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
安装后直接运行 `procproxy` 会显示首次使用指引,不依赖任何样例文件。
|
|
81
|
+
Python SDK 也一样:`start_proxy()` 不传配置即可启动(内置 `backend=null`),
|
|
82
|
+
不需要先跑 `init`;`init` 只用于生成可编辑配置。
|
|
81
83
|
|
|
82
84
|
非管理员:WinDivert 抓包必须管理员;可以用 `--backend null` 做转发链路联调,
|
|
83
85
|
或在服务已启动、且配置了 `service.ipc_allow_group` 时读取状态。
|
|
@@ -344,24 +346,151 @@ rules: # 自上而下,**首个命中生
|
|
|
344
346
|
|
|
345
347
|
## 编程接口
|
|
346
348
|
|
|
349
|
+
`proxycore.api` 是与 CLI 共用同一套配置、规则引擎和会话表的 SDK。
|
|
350
|
+
完整教程见 [`docs/15-编程接口与SDK.md`](docs/15-编程接口与SDK.md)。
|
|
351
|
+
|
|
352
|
+
### 30 秒:零配置启动
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from proxycore.api import start_proxy
|
|
356
|
+
|
|
357
|
+
with start_proxy() as ppx: # 不需要任何配置文件
|
|
358
|
+
print(ppx.config_source) # "packaged"
|
|
359
|
+
print(ppx.config_path) # .../proxycore/data/default.yaml
|
|
360
|
+
print(ppx.status()) # 后端 / 规则 / 会话 / UDP / 配置来源
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
不传配置时的来源优先级:
|
|
364
|
+
|
|
365
|
+
| 优先级 | 来源 | 说明 |
|
|
366
|
+
|---|---|---|
|
|
367
|
+
| 1 | `start_proxy(path)` | 显式路径(也接受内存中的 `Config` 对象) |
|
|
368
|
+
| 2 | `PROCPROXY_CONFIG` | 指向不存在文件会直接报错,不回退 |
|
|
369
|
+
| 3 | `~/.procproxy/config.yaml` | `procproxy init` 生成的用户配置 |
|
|
370
|
+
| 4 | 包内 `data/default.yaml` | `backend=null`,不抓包、不改道 |
|
|
371
|
+
|
|
372
|
+
内置配置**不会**监听 sink,也**不会**创建任何文件。它适合装配、规则决策、
|
|
373
|
+
状态查询和联调;要真实抓包请用 `backend="windivert"`(需管理员)或
|
|
374
|
+
`procproxy init` 生成自己的配置。
|
|
375
|
+
|
|
376
|
+
### 显式配置与测试转发
|
|
377
|
+
|
|
347
378
|
```python
|
|
348
|
-
from proxycore import
|
|
379
|
+
from proxycore.api import start_proxy
|
|
349
380
|
|
|
350
|
-
#
|
|
351
|
-
with
|
|
352
|
-
print(
|
|
381
|
+
# 显式路径 + 真实抓包(需要管理员)
|
|
382
|
+
with start_proxy(r"D:\cfg\procproxy.yaml", backend="windivert") as ppx:
|
|
383
|
+
print(ppx.sink) # sink 监听地址
|
|
384
|
+
print(ppx.sessions(limit=20))
|
|
353
385
|
|
|
354
|
-
#
|
|
355
|
-
with
|
|
356
|
-
|
|
386
|
+
# 联调模式:backend=null + 把 sink 收到的连接固定转发到目标
|
|
387
|
+
with start_proxy(backend="null", sink_test_target="127.0.0.1:8080",
|
|
388
|
+
upstream="local-mock") as ppx:
|
|
357
389
|
...
|
|
390
|
+
# curl -x http://127.0.0.1:19080 http://any/ → 127.0.0.1:8080
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### 在代码里构建配置
|
|
394
|
+
|
|
395
|
+
可以从字典构造 `Config`,也可以先落盘再按路径加载:
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
from proxycore.config import Config, atomic_write
|
|
399
|
+
from proxycore.api import start_proxy
|
|
400
|
+
|
|
401
|
+
doc = {
|
|
402
|
+
"version": 1,
|
|
403
|
+
"service": {"sink": {"host": "127.0.0.1", "port": 19080}, "backend": "null"},
|
|
404
|
+
"profile": {
|
|
405
|
+
"name": "myapp",
|
|
406
|
+
"default_action": "direct",
|
|
407
|
+
"dns_mode": "remote",
|
|
408
|
+
"rules": [
|
|
409
|
+
{"id": "r1", "action": "proxy", "upstream": "office",
|
|
410
|
+
"targets": "chrome.exe;msedge.exe", "dst_port": "443,80"},
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
"upstreams": [
|
|
414
|
+
{"alias": "office", "kind": "socks5", "host": "127.0.0.1", "port": 1080},
|
|
415
|
+
],
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
cfg = Config.from_dict(doc)
|
|
419
|
+
cfg.validate() # from_dict 只解析,不校验
|
|
420
|
+
atomic_write(cfg, "myconfig.yaml") # 临时文件 → 校验 → 原子替换
|
|
421
|
+
|
|
422
|
+
with start_proxy("myconfig.yaml") as ppx:
|
|
423
|
+
...
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
> `Config.from_dict()` 不会解析 `password_ref`;需要凭据时请用
|
|
427
|
+
> `Config.load()` / `start_proxy(path)`,或自己给 `upstream.password` 赋值。
|
|
358
428
|
|
|
359
|
-
|
|
360
|
-
|
|
429
|
+
### 只做决策,不启动数据面
|
|
430
|
+
|
|
431
|
+
```python
|
|
432
|
+
from proxycore.api import make_runtime
|
|
433
|
+
|
|
434
|
+
runtime = make_runtime() # 默认用内置配置,backend=null
|
|
361
435
|
decision = runtime.decide(conn_request)
|
|
436
|
+
print(decision.action, decision.reason)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### 生命周期与句柄
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
ppx = start_proxy()
|
|
443
|
+
try:
|
|
444
|
+
print(ppx.status())
|
|
445
|
+
finally:
|
|
446
|
+
ppx.stop() # 幂等;with 退出时也会自动调用
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
| 成员 | 说明 |
|
|
450
|
+
|---|---|
|
|
451
|
+
| `status()` | 后端、规则、会话、熔断、DNS、UDP、sink、`config_path`、`config_source` |
|
|
452
|
+
| `sessions(limit=50)` / `active_sessions()` | 会话明细 / 活跃数 |
|
|
453
|
+
| `reload(path="")` | 热重载;不传路径则重读当前文件。`sink.port`/`backend` 变更需重启 |
|
|
454
|
+
| `decide(conn)` | 只问规则引擎,不抓包、不改道 |
|
|
455
|
+
| `profile` | 当前生效的 `Profile` |
|
|
456
|
+
| `config_path` / `config_source` | 当前配置的绝对路径与来源 |
|
|
457
|
+
| `running` | 是否仍在运行 |
|
|
458
|
+
|
|
459
|
+
`start_proxy()` 会启动后台维护线程;`run_forever()` 会阻塞到 Ctrl+C。
|
|
460
|
+
请在主线程使用,并在退出时 `stop()`(或使用 `with`)。
|
|
461
|
+
|
|
462
|
+
### 错误处理
|
|
463
|
+
|
|
464
|
+
配置 / 后端 / 启动失败统一抛 `ProxyStartError`,`code` 是稳定的机器可读标识:
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
from proxycore.api import ProxyStartError, start_proxy
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
start_proxy("missing.yaml")
|
|
471
|
+
except ProxyStartError as exc:
|
|
472
|
+
print(exc.code) # config_not_found
|
|
362
473
|
```
|
|
363
474
|
|
|
364
|
-
|
|
475
|
+
| code | 含义 |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `config_not_found` | 配置文件不存在(显式路径 / 环境变量不会回退) |
|
|
478
|
+
| `config_unreadable` | 文件存在但读不了(权限、目录等) |
|
|
479
|
+
| `config_parse_invalid` | YAML 语法错误 / 根节点非映射 / 非 UTF-8 |
|
|
480
|
+
| `config_version_unsupported` | `version != 1` |
|
|
481
|
+
| `rule_upstream_missing` 等 | 规则或上游校验失败(完整列表见文档) |
|
|
482
|
+
| `E_BACKEND_UNAVAILABLE` | 后端不可用(未知后端、WinDivert 不可加载等) |
|
|
483
|
+
|
|
484
|
+
`api.load_config()` 同样抛 `ProxyStartError`,不是 `ConfigError`。
|
|
485
|
+
|
|
486
|
+
### 环境变量
|
|
487
|
+
|
|
488
|
+
| 变量 | 作用 |
|
|
489
|
+
|---|---|
|
|
490
|
+
| `PROCPROXY_CONFIG` | 配置文件路径;显式路径之后、用户配置之前生效 |
|
|
491
|
+
| `PROCPROXY_RUNTIME_DIR` | 运行时状态/日志目录(CLI 与服务使用) |
|
|
492
|
+
|
|
493
|
+
跑 `python tests/tools/api_demo.py` 看完整演示。
|
|
365
494
|
|
|
366
495
|
---
|
|
367
496
|
|
|
@@ -406,12 +535,13 @@ src/proxycore/ 核心包
|
|
|
406
535
|
├── tunnel/ sink + relay(本地落地与双向转发)
|
|
407
536
|
├── upstream/ SOCKS5 / SOCKS4 / HTTP CONNECT / 跳链
|
|
408
537
|
├── dns/ DNS 三模式与 UDP
|
|
538
|
+
├── config_source.py 配置来源解析(显式 / env / 用户 / 样例 / 内置)
|
|
409
539
|
├── forward.py ← procproxy.py <进程名> 的实现
|
|
410
540
|
├── canproxy.py / checkroute.py / probe.py / pathsetup.py ← 其余子命令
|
|
411
541
|
└── service/ ipc.py Windows 服务 + 命名管道
|
|
412
542
|
samples/ mock 服务 + 示例配置
|
|
413
|
-
docs/ 文档(00
|
|
414
|
-
tests/ 测试套件(
|
|
543
|
+
docs/ 文档(00 架构 / 07 规则 / 11 排错 / 14 抓包 / 15 SDK)
|
|
544
|
+
tests/ 测试套件(619 passed / 4 skipped)
|
|
415
545
|
└── tools/ 验证脚本(run_demo / run_e2e / bench / …)
|
|
416
546
|
```
|
|
417
547
|
|
|
@@ -442,7 +572,7 @@ tests/ 测试套件(555 passed)
|
|
|
442
572
|
|
|
443
573
|
```bash
|
|
444
574
|
pip install -e ".[dev]"
|
|
445
|
-
python -m pytest -q tests/ #
|
|
575
|
+
python -m pytest -q tests/ # 619 passed / 4 skipped(非管理员 Windows)
|
|
446
576
|
python -m ruff check src tests
|
|
447
577
|
python -m mypy src/proxycore
|
|
448
578
|
```
|