procproxy 0.1.1__tar.gz → 0.1.3__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 (54) hide show
  1. {procproxy-0.1.1/src/procproxy.egg-info → procproxy-0.1.3}/PKG-INFO +194 -18
  2. {procproxy-0.1.1 → procproxy-0.1.3}/README.md +193 -17
  3. {procproxy-0.1.1 → procproxy-0.1.3}/pyproject.toml +1 -1
  4. {procproxy-0.1.1 → procproxy-0.1.3/src/procproxy.egg-info}/PKG-INFO +194 -18
  5. {procproxy-0.1.1 → procproxy-0.1.3}/src/procproxy.egg-info/SOURCES.txt +2 -0
  6. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/__init__.py +1 -1
  7. procproxy-0.1.3/src/proxycore/api.py +345 -0
  8. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/canproxy.py +22 -20
  9. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/checkroute.py +14 -10
  10. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/cli.py +81 -101
  11. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/config.py +30 -6
  12. procproxy-0.1.3/src/proxycore/config_init.py +85 -0
  13. procproxy-0.1.3/src/proxycore/config_source.py +154 -0
  14. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/service/__init__.py +11 -2
  15. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/service/__main__.py +19 -5
  16. procproxy-0.1.1/src/proxycore/api.py +0 -226
  17. {procproxy-0.1.1 → procproxy-0.1.3}/LICENSE +0 -0
  18. {procproxy-0.1.1 → procproxy-0.1.3}/setup.cfg +0 -0
  19. {procproxy-0.1.1 → procproxy-0.1.3}/src/procproxy.egg-info/dependency_links.txt +0 -0
  20. {procproxy-0.1.1 → procproxy-0.1.3}/src/procproxy.egg-info/entry_points.txt +0 -0
  21. {procproxy-0.1.1 → procproxy-0.1.3}/src/procproxy.egg-info/requires.txt +0 -0
  22. {procproxy-0.1.1 → procproxy-0.1.3}/src/procproxy.egg-info/top_level.txt +0 -0
  23. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/__main__.py +0 -0
  24. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/backend/__init__.py +0 -0
  25. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/backend/base.py +0 -0
  26. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/backend/null.py +0 -0
  27. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/backend/windivert/__init__.py +0 -0
  28. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/backend/windivert/filter.py +0 -0
  29. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/backend/windivert/pidmap.py +0 -0
  30. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/backend/windivert/pkt.py +0 -0
  31. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/data/default.yaml +0 -0
  32. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/dns/__init__.py +0 -0
  33. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/dns/cache.py +0 -0
  34. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/dns/modes.py +0 -0
  35. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/dns/udp.py +0 -0
  36. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/forward.py +0 -0
  37. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/ipc.py +0 -0
  38. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/log.py +0 -0
  39. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/pathsetup.py +0 -0
  40. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/probe.py +0 -0
  41. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/proc.py +0 -0
  42. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/rules.py +0 -0
  43. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/runtime.py +0 -0
  44. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/tunnel/__init__.py +0 -0
  45. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/tunnel/relay.py +0 -0
  46. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/tunnel/sessions.py +0 -0
  47. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/tunnel/sink.py +0 -0
  48. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/types.py +0 -0
  49. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/upstream/__init__.py +0 -0
  50. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/upstream/base.py +0 -0
  51. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/upstream/chain.py +0 -0
  52. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/upstream/http_connect.py +0 -0
  53. {procproxy-0.1.1 → procproxy-0.1.3}/src/proxycore/upstream/socks4.py +0 -0
  54. {procproxy-0.1.1 → procproxy-0.1.3}/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.1
3
+ Version: 0.1.3
4
4
  Summary: ProcProxy —— 进程级代理:按进程决定哪条连接走哪个上游代理
5
5
  Author: Longersking
6
6
  License-Expression: MIT
@@ -74,10 +74,12 @@ python procproxy.py doctor | rules | sessions | ... # 子命令(等价于 pyt
74
74
  pip install procproxy
75
75
  procproxy init # 生成 ~/.procproxy/config.yaml
76
76
  procproxy doctor # 环境自检
77
- procproxy forward WeChat.exe --port 8848 # 真实转发(需管理员)
77
+ procproxy forward --process 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` 时读取状态。
@@ -248,7 +250,7 @@ python procproxy.py Weixin.exe WeChatAppEx.exe --port 8848
248
250
  ⚠️ 只有 ① 没有 ② → 端口填错了(**要填抓包工具的 MITM 监听口,不是它的 Web 管理口**)。
249
251
  ① ② 都正常但内容看不懂 → 那是 TLS,不是转发的问题。
250
252
 
251
- 完整指南(含排错与参数表)见 [`docs/14-把进程流量转发给抓包工具.md`](docs/14-把进程流量转发给抓包工具.md)。
253
+ 完整指南(含排错与参数表)见 [`docs/14-把进程流量转发给抓包工具.md`](https://github.com/Longersking/procproxy/blob/master/docs/14-%E6%8A%8A%E8%BF%9B%E7%A8%8B%E6%B5%81%E9%87%8F%E8%BD%AC%E5%8F%91%E7%BB%99%E6%8A%93%E5%8C%85%E5%B7%A5%E5%85%B7.md)。
252
254
 
253
255
  ---
254
256
 
@@ -338,30 +340,203 @@ rules: # 自上而下,**首个命中生
338
340
  7. 都没命中 → profile.default_action
339
341
  ```
340
342
 
341
- 完整语义见 [`docs/07-规则引擎与Profile格式.md`](docs/07-规则引擎与Profile格式.md)。
343
+ 完整语义见 [`docs/07-规则引擎与Profile格式.md`](https://github.com/Longersking/procproxy/blob/master/docs/07-%E8%A7%84%E5%88%99%E5%BC%95%E6%93%8E%E4%B8%8EProfile%E6%A0%BC%E5%BC%8F.md)。
342
344
 
343
345
  ---
344
346
 
345
347
  ## 编程接口
346
348
 
349
+ `proxycore.api` 是与 CLI 共用同一套配置、规则引擎和会话表的 SDK。
350
+ 完整教程见 [`docs/15-编程接口与SDK.md`](https://github.com/Longersking/procproxy/blob/master/docs/15-%E7%BC%96%E7%A8%8B%E6%8E%A5%E5%8F%A3%E4%B8%8ESDK.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
+
378
+ ```python
379
+ from proxycore.api import init_config
380
+
381
+ path = init_config(
382
+ "D:/cfg/procproxy.yaml", # 省略则用 PROCPROXY_CONFIG 或 ~/.procproxy/config.yaml
383
+ backend="windivert", # 真抓包需管理员;联调用 "null"
384
+ sink_port=19080,
385
+ )
386
+ print(path) # 绝对路径;等价于 procproxy init
387
+ ```
388
+
389
+ 生成的是最小模板,`upstreams` / `rules` 仍为空,需要按下面的案例补上。
390
+
391
+ ### 代理一个进程(最小完整配置)
392
+
393
+ ```yaml
394
+ version: 1
395
+ service:
396
+ sink: { host: 127.0.0.1, port: 19080 }
397
+ backend: windivert # 真抓包;联调改 null
398
+ profile:
399
+ name: chrome-via-socks
400
+ default_action: direct
401
+ dns_mode: remote
402
+ upstreams:
403
+ - { alias: socks, kind: socks5, host: 127.0.0.1, port: 1080, auth: none }
404
+ rules:
405
+ - { id: r-private, action: direct, targets: "*",
406
+ dst_ip: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8" }
407
+ - { id: r-chrome, action: proxy, upstream: socks,
408
+ targets: "chrome.exe", dst_port: "*" }
409
+ ```
410
+
347
411
  ```python
348
- from proxycore import api
412
+ from proxycore.api import start_proxy
349
413
 
350
- # 上下文管理器:退出自动停
351
- with api.start_proxy("samples/profiles/dev.yaml") as handle:
352
- print(handle.status())
414
+ with start_proxy("D:/cfg/chrome.yaml") as ppx: # 管理员终端
415
+ print(ppx.status())
416
+ print(ppx.sessions(limit=20))
417
+ ```
418
+
419
+ 规则自上而下、首个命中生效;启动后只影响**新建立**的连接。
420
+ 完整步骤、验证方法与常见坑见 [`docs/15-编程接口与SDK.md`](https://github.com/Longersking/procproxy/blob/master/docs/15-%E7%BC%96%E7%A8%8B%E6%8E%A5%E5%8F%A3%E4%B8%8ESDK.md)。
353
421
 
354
- # 测试模式:sink 把所有连接转发到固定目标(不做抓包)
355
- with api.start_proxy("samples/profiles/dev.yaml", backend="null",
356
- sink_test_target="127.0.0.1:8080") as handle:
422
+ ### 显式配置与测试转发
423
+
424
+ ```python
425
+ from proxycore.api import start_proxy
426
+
427
+ # 显式路径 + 真实抓包(需要管理员)
428
+ with start_proxy(r"D:\cfg\procproxy.yaml", backend="windivert") as ppx:
429
+ print(ppx.sink) # sink 监听地址
430
+ print(ppx.sessions(limit=20))
431
+
432
+ # 联调模式:backend=null + 把 sink 收到的连接固定转发到目标
433
+ with start_proxy(backend="null", sink_test_target="127.0.0.1:8080",
434
+ upstream="local-mock") as ppx:
357
435
  ...
436
+ # curl -x http://127.0.0.1:19080 http://any/ → 127.0.0.1:8080
437
+ ```
358
438
 
359
- # 只要规则决策,不要监听
360
- runtime = api.make_runtime("samples/profiles/dev.yaml", backend="null")
439
+ ### 在代码里构建配置
440
+
441
+ 可以从字典构造 `Config`,也可以先落盘再按路径加载:
442
+
443
+ ```python
444
+ from proxycore.config import Config, atomic_write
445
+ from proxycore.api import start_proxy
446
+
447
+ doc = {
448
+ "version": 1,
449
+ "service": {"sink": {"host": "127.0.0.1", "port": 19080}, "backend": "null"},
450
+ "profile": {
451
+ "name": "myapp",
452
+ "default_action": "direct",
453
+ "dns_mode": "remote",
454
+ "rules": [
455
+ {"id": "r1", "action": "proxy", "upstream": "office",
456
+ "targets": "chrome.exe;msedge.exe", "dst_port": "443,80"},
457
+ ],
458
+ },
459
+ "upstreams": [
460
+ {"alias": "office", "kind": "socks5", "host": "127.0.0.1", "port": 1080},
461
+ ],
462
+ }
463
+
464
+ cfg = Config.from_dict(doc)
465
+ cfg.validate() # from_dict 只解析,不校验
466
+ atomic_write(cfg, "myconfig.yaml") # 临时文件 → 校验 → 原子替换
467
+
468
+ with start_proxy("myconfig.yaml") as ppx:
469
+ ...
470
+ ```
471
+
472
+ > `Config.from_dict()` 不会解析 `password_ref`;需要凭据时请用
473
+ > `Config.load()` / `start_proxy(path)`,或自己给 `upstream.password` 赋值。
474
+
475
+ ### 只做决策,不启动数据面
476
+
477
+ ```python
478
+ from proxycore.api import make_runtime
479
+
480
+ runtime = make_runtime() # 默认用内置配置,backend=null
361
481
  decision = runtime.decide(conn_request)
482
+ print(decision.action, decision.reason)
362
483
  ```
363
484
 
364
- 跑 `python tests/tools/api_demo.py` 看三种用法的完整演示。
485
+ ### 生命周期与句柄
486
+
487
+ ```python
488
+ ppx = start_proxy()
489
+ try:
490
+ print(ppx.status())
491
+ finally:
492
+ ppx.stop() # 幂等;with 退出时也会自动调用
493
+ ```
494
+
495
+ | 成员 | 说明 |
496
+ |---|---|
497
+ | `status()` | 后端、规则、会话、熔断、DNS、UDP、sink、`config_path`、`config_source` |
498
+ | `sessions(limit=50)` / `active_sessions()` | 会话明细 / 活跃数 |
499
+ | `reload(path="")` | 热重载;不传路径则重读当前文件。`sink.port`/`backend` 变更需重启 |
500
+ | `decide(conn)` | 只问规则引擎,不抓包、不改道 |
501
+ | `profile` | 当前生效的 `Profile` |
502
+ | `config_path` / `config_source` | 当前配置的绝对路径与来源 |
503
+ | `running` | 是否仍在运行 |
504
+
505
+ `start_proxy()` 会启动后台维护线程;`run_forever()` 会阻塞到 Ctrl+C。
506
+ 请在主线程使用,并在退出时 `stop()`(或使用 `with`)。
507
+
508
+ ### 错误处理
509
+
510
+ 配置 / 后端 / 启动失败统一抛 `ProxyStartError`,`code` 是稳定的机器可读标识:
511
+
512
+ ```python
513
+ from proxycore.api import ProxyStartError, start_proxy
514
+
515
+ try:
516
+ start_proxy("missing.yaml")
517
+ except ProxyStartError as exc:
518
+ print(exc.code) # config_not_found
519
+ ```
520
+
521
+ | code | 含义 |
522
+ |---|---|
523
+ | `config_not_found` | 配置文件不存在(显式路径 / 环境变量不会回退) |
524
+ | `config_unreadable` | 文件存在但读不了(权限、目录等) |
525
+ | `config_parse_invalid` | YAML 语法错误 / 根节点非映射 / 非 UTF-8 |
526
+ | `config_version_unsupported` | `version != 1` |
527
+ | `rule_upstream_missing` 等 | 规则或上游校验失败(完整列表见文档) |
528
+ | `E_BACKEND_UNAVAILABLE` | 后端不可用(未知后端、WinDivert 不可加载等) |
529
+
530
+ `api.load_config()` 同样抛 `ProxyStartError`,不是 `ConfigError`。
531
+
532
+ ### 环境变量
533
+
534
+ | 变量 | 作用 |
535
+ |---|---|
536
+ | `PROCPROXY_CONFIG` | 配置文件路径;显式路径之后、用户配置之前生效 |
537
+ | `PROCPROXY_RUNTIME_DIR` | 运行时状态/日志目录(CLI 与服务使用) |
538
+
539
+ 跑 `python tests/tools/api_demo.py` 看完整演示。
365
540
 
366
541
  ---
367
542
 
@@ -406,12 +581,13 @@ src/proxycore/ 核心包
406
581
  ├── tunnel/ sink + relay(本地落地与双向转发)
407
582
  ├── upstream/ SOCKS5 / SOCKS4 / HTTP CONNECT / 跳链
408
583
  ├── dns/ DNS 三模式与 UDP
584
+ ├── config_source.py 配置来源解析(显式 / env / 用户 / 样例 / 内置)
409
585
  ├── forward.py ← procproxy.py <进程名> 的实现
410
586
  ├── canproxy.py / checkroute.py / probe.py / pathsetup.py ← 其余子命令
411
587
  └── service/ ipc.py Windows 服务 + 命名管道
412
588
  samples/ mock 服务 + 示例配置
413
- docs/ 文档(00 架构与阅读路线 / 07 规则语义 / 11 排错 / 14 转发到抓包工具)
414
- tests/ 测试套件(555 passed)
589
+ docs/ 文档(00 架构 / 07 规则 / 11 排错 / 14 抓包 / 15 SDK)
590
+ tests/ 测试套件(624 passed / 4 skipped)
415
591
  └── tools/ 验证脚本(run_demo / run_e2e / bench / …)
416
592
  ```
417
593
 
@@ -432,7 +608,7 @@ tests/ 测试套件(555 passed)
432
608
 
433
609
  ## 排错
434
610
 
435
- 见 [`docs/11-调试排错手册.md`](docs/11-调试排错手册.md)(症状 → 定位 → 解决)。
611
+ 见 [`docs/11-调试排错手册.md`](https://github.com/Longersking/procproxy/blob/master/docs/11-%E8%B0%83%E8%AF%95%E6%8E%92%E9%94%99%E6%89%8B%E5%86%8C.md)(症状 → 定位 → 解决)。
436
612
 
437
613
  最常见的一条:**`[WinError 5] 拒绝访问` = 不是管理员**。
438
614
 
@@ -442,7 +618,7 @@ tests/ 测试套件(555 passed)
442
618
 
443
619
  ```bash
444
620
  pip install -e ".[dev]"
445
- python -m pytest -q tests/ # 555 passed / 4 skipped(非管理员 Windows)
621
+ python -m pytest -q tests/ # 624 passed / 4 skipped(非管理员 Windows)
446
622
  python -m ruff check src tests
447
623
  python -m mypy src/proxycore
448
624
  ```
@@ -35,10 +35,12 @@ python procproxy.py doctor | rules | sessions | ... # 子命令(等价于 pyt
35
35
  pip install procproxy
36
36
  procproxy init # 生成 ~/.procproxy/config.yaml
37
37
  procproxy doctor # 环境自检
38
- procproxy forward WeChat.exe --port 8848 # 真实转发(需管理员)
38
+ procproxy forward --process 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` 时读取状态。
@@ -209,7 +211,7 @@ python procproxy.py Weixin.exe WeChatAppEx.exe --port 8848
209
211
  ⚠️ 只有 ① 没有 ② → 端口填错了(**要填抓包工具的 MITM 监听口,不是它的 Web 管理口**)。
210
212
  ① ② 都正常但内容看不懂 → 那是 TLS,不是转发的问题。
211
213
 
212
- 完整指南(含排错与参数表)见 [`docs/14-把进程流量转发给抓包工具.md`](docs/14-把进程流量转发给抓包工具.md)。
214
+ 完整指南(含排错与参数表)见 [`docs/14-把进程流量转发给抓包工具.md`](https://github.com/Longersking/procproxy/blob/master/docs/14-%E6%8A%8A%E8%BF%9B%E7%A8%8B%E6%B5%81%E9%87%8F%E8%BD%AC%E5%8F%91%E7%BB%99%E6%8A%93%E5%8C%85%E5%B7%A5%E5%85%B7.md)。
213
215
 
214
216
  ---
215
217
 
@@ -299,30 +301,203 @@ rules: # 自上而下,**首个命中生
299
301
  7. 都没命中 → profile.default_action
300
302
  ```
301
303
 
302
- 完整语义见 [`docs/07-规则引擎与Profile格式.md`](docs/07-规则引擎与Profile格式.md)。
304
+ 完整语义见 [`docs/07-规则引擎与Profile格式.md`](https://github.com/Longersking/procproxy/blob/master/docs/07-%E8%A7%84%E5%88%99%E5%BC%95%E6%93%8E%E4%B8%8EProfile%E6%A0%BC%E5%BC%8F.md)。
303
305
 
304
306
  ---
305
307
 
306
308
  ## 编程接口
307
309
 
310
+ `proxycore.api` 是与 CLI 共用同一套配置、规则引擎和会话表的 SDK。
311
+ 完整教程见 [`docs/15-编程接口与SDK.md`](https://github.com/Longersking/procproxy/blob/master/docs/15-%E7%BC%96%E7%A8%8B%E6%8E%A5%E5%8F%A3%E4%B8%8ESDK.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
+
339
+ ```python
340
+ from proxycore.api import init_config
341
+
342
+ path = init_config(
343
+ "D:/cfg/procproxy.yaml", # 省略则用 PROCPROXY_CONFIG 或 ~/.procproxy/config.yaml
344
+ backend="windivert", # 真抓包需管理员;联调用 "null"
345
+ sink_port=19080,
346
+ )
347
+ print(path) # 绝对路径;等价于 procproxy init
348
+ ```
349
+
350
+ 生成的是最小模板,`upstreams` / `rules` 仍为空,需要按下面的案例补上。
351
+
352
+ ### 代理一个进程(最小完整配置)
353
+
354
+ ```yaml
355
+ version: 1
356
+ service:
357
+ sink: { host: 127.0.0.1, port: 19080 }
358
+ backend: windivert # 真抓包;联调改 null
359
+ profile:
360
+ name: chrome-via-socks
361
+ default_action: direct
362
+ dns_mode: remote
363
+ upstreams:
364
+ - { alias: socks, kind: socks5, host: 127.0.0.1, port: 1080, auth: none }
365
+ rules:
366
+ - { id: r-private, action: direct, targets: "*",
367
+ dst_ip: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8" }
368
+ - { id: r-chrome, action: proxy, upstream: socks,
369
+ targets: "chrome.exe", dst_port: "*" }
370
+ ```
371
+
308
372
  ```python
309
- from proxycore import api
373
+ from proxycore.api import start_proxy
310
374
 
311
- # 上下文管理器:退出自动停
312
- with api.start_proxy("samples/profiles/dev.yaml") as handle:
313
- print(handle.status())
375
+ with start_proxy("D:/cfg/chrome.yaml") as ppx: # 管理员终端
376
+ print(ppx.status())
377
+ print(ppx.sessions(limit=20))
378
+ ```
379
+
380
+ 规则自上而下、首个命中生效;启动后只影响**新建立**的连接。
381
+ 完整步骤、验证方法与常见坑见 [`docs/15-编程接口与SDK.md`](https://github.com/Longersking/procproxy/blob/master/docs/15-%E7%BC%96%E7%A8%8B%E6%8E%A5%E5%8F%A3%E4%B8%8ESDK.md)。
314
382
 
315
- # 测试模式:sink 把所有连接转发到固定目标(不做抓包)
316
- with api.start_proxy("samples/profiles/dev.yaml", backend="null",
317
- sink_test_target="127.0.0.1:8080") as handle:
383
+ ### 显式配置与测试转发
384
+
385
+ ```python
386
+ from proxycore.api import start_proxy
387
+
388
+ # 显式路径 + 真实抓包(需要管理员)
389
+ with start_proxy(r"D:\cfg\procproxy.yaml", backend="windivert") as ppx:
390
+ print(ppx.sink) # sink 监听地址
391
+ print(ppx.sessions(limit=20))
392
+
393
+ # 联调模式:backend=null + 把 sink 收到的连接固定转发到目标
394
+ with start_proxy(backend="null", sink_test_target="127.0.0.1:8080",
395
+ upstream="local-mock") as ppx:
318
396
  ...
397
+ # curl -x http://127.0.0.1:19080 http://any/ → 127.0.0.1:8080
398
+ ```
319
399
 
320
- # 只要规则决策,不要监听
321
- runtime = api.make_runtime("samples/profiles/dev.yaml", backend="null")
400
+ ### 在代码里构建配置
401
+
402
+ 可以从字典构造 `Config`,也可以先落盘再按路径加载:
403
+
404
+ ```python
405
+ from proxycore.config import Config, atomic_write
406
+ from proxycore.api import start_proxy
407
+
408
+ doc = {
409
+ "version": 1,
410
+ "service": {"sink": {"host": "127.0.0.1", "port": 19080}, "backend": "null"},
411
+ "profile": {
412
+ "name": "myapp",
413
+ "default_action": "direct",
414
+ "dns_mode": "remote",
415
+ "rules": [
416
+ {"id": "r1", "action": "proxy", "upstream": "office",
417
+ "targets": "chrome.exe;msedge.exe", "dst_port": "443,80"},
418
+ ],
419
+ },
420
+ "upstreams": [
421
+ {"alias": "office", "kind": "socks5", "host": "127.0.0.1", "port": 1080},
422
+ ],
423
+ }
424
+
425
+ cfg = Config.from_dict(doc)
426
+ cfg.validate() # from_dict 只解析,不校验
427
+ atomic_write(cfg, "myconfig.yaml") # 临时文件 → 校验 → 原子替换
428
+
429
+ with start_proxy("myconfig.yaml") as ppx:
430
+ ...
431
+ ```
432
+
433
+ > `Config.from_dict()` 不会解析 `password_ref`;需要凭据时请用
434
+ > `Config.load()` / `start_proxy(path)`,或自己给 `upstream.password` 赋值。
435
+
436
+ ### 只做决策,不启动数据面
437
+
438
+ ```python
439
+ from proxycore.api import make_runtime
440
+
441
+ runtime = make_runtime() # 默认用内置配置,backend=null
322
442
  decision = runtime.decide(conn_request)
443
+ print(decision.action, decision.reason)
323
444
  ```
324
445
 
325
- 跑 `python tests/tools/api_demo.py` 看三种用法的完整演示。
446
+ ### 生命周期与句柄
447
+
448
+ ```python
449
+ ppx = start_proxy()
450
+ try:
451
+ print(ppx.status())
452
+ finally:
453
+ ppx.stop() # 幂等;with 退出时也会自动调用
454
+ ```
455
+
456
+ | 成员 | 说明 |
457
+ |---|---|
458
+ | `status()` | 后端、规则、会话、熔断、DNS、UDP、sink、`config_path`、`config_source` |
459
+ | `sessions(limit=50)` / `active_sessions()` | 会话明细 / 活跃数 |
460
+ | `reload(path="")` | 热重载;不传路径则重读当前文件。`sink.port`/`backend` 变更需重启 |
461
+ | `decide(conn)` | 只问规则引擎,不抓包、不改道 |
462
+ | `profile` | 当前生效的 `Profile` |
463
+ | `config_path` / `config_source` | 当前配置的绝对路径与来源 |
464
+ | `running` | 是否仍在运行 |
465
+
466
+ `start_proxy()` 会启动后台维护线程;`run_forever()` 会阻塞到 Ctrl+C。
467
+ 请在主线程使用,并在退出时 `stop()`(或使用 `with`)。
468
+
469
+ ### 错误处理
470
+
471
+ 配置 / 后端 / 启动失败统一抛 `ProxyStartError`,`code` 是稳定的机器可读标识:
472
+
473
+ ```python
474
+ from proxycore.api import ProxyStartError, start_proxy
475
+
476
+ try:
477
+ start_proxy("missing.yaml")
478
+ except ProxyStartError as exc:
479
+ print(exc.code) # config_not_found
480
+ ```
481
+
482
+ | code | 含义 |
483
+ |---|---|
484
+ | `config_not_found` | 配置文件不存在(显式路径 / 环境变量不会回退) |
485
+ | `config_unreadable` | 文件存在但读不了(权限、目录等) |
486
+ | `config_parse_invalid` | YAML 语法错误 / 根节点非映射 / 非 UTF-8 |
487
+ | `config_version_unsupported` | `version != 1` |
488
+ | `rule_upstream_missing` 等 | 规则或上游校验失败(完整列表见文档) |
489
+ | `E_BACKEND_UNAVAILABLE` | 后端不可用(未知后端、WinDivert 不可加载等) |
490
+
491
+ `api.load_config()` 同样抛 `ProxyStartError`,不是 `ConfigError`。
492
+
493
+ ### 环境变量
494
+
495
+ | 变量 | 作用 |
496
+ |---|---|
497
+ | `PROCPROXY_CONFIG` | 配置文件路径;显式路径之后、用户配置之前生效 |
498
+ | `PROCPROXY_RUNTIME_DIR` | 运行时状态/日志目录(CLI 与服务使用) |
499
+
500
+ 跑 `python tests/tools/api_demo.py` 看完整演示。
326
501
 
327
502
  ---
328
503
 
@@ -367,12 +542,13 @@ src/proxycore/ 核心包
367
542
  ├── tunnel/ sink + relay(本地落地与双向转发)
368
543
  ├── upstream/ SOCKS5 / SOCKS4 / HTTP CONNECT / 跳链
369
544
  ├── dns/ DNS 三模式与 UDP
545
+ ├── config_source.py 配置来源解析(显式 / env / 用户 / 样例 / 内置)
370
546
  ├── forward.py ← procproxy.py <进程名> 的实现
371
547
  ├── canproxy.py / checkroute.py / probe.py / pathsetup.py ← 其余子命令
372
548
  └── service/ ipc.py Windows 服务 + 命名管道
373
549
  samples/ mock 服务 + 示例配置
374
- docs/ 文档(00 架构与阅读路线 / 07 规则语义 / 11 排错 / 14 转发到抓包工具)
375
- tests/ 测试套件(555 passed)
550
+ docs/ 文档(00 架构 / 07 规则 / 11 排错 / 14 抓包 / 15 SDK)
551
+ tests/ 测试套件(624 passed / 4 skipped)
376
552
  └── tools/ 验证脚本(run_demo / run_e2e / bench / …)
377
553
  ```
378
554
 
@@ -393,7 +569,7 @@ tests/ 测试套件(555 passed)
393
569
 
394
570
  ## 排错
395
571
 
396
- 见 [`docs/11-调试排错手册.md`](docs/11-调试排错手册.md)(症状 → 定位 → 解决)。
572
+ 见 [`docs/11-调试排错手册.md`](https://github.com/Longersking/procproxy/blob/master/docs/11-%E8%B0%83%E8%AF%95%E6%8E%92%E9%94%99%E6%89%8B%E5%86%8C.md)(症状 → 定位 → 解决)。
397
573
 
398
574
  最常见的一条:**`[WinError 5] 拒绝访问` = 不是管理员**。
399
575
 
@@ -403,7 +579,7 @@ tests/ 测试套件(555 passed)
403
579
 
404
580
  ```bash
405
581
  pip install -e ".[dev]"
406
- python -m pytest -q tests/ # 555 passed / 4 skipped(非管理员 Windows)
582
+ python -m pytest -q tests/ # 624 passed / 4 skipped(非管理员 Windows)
407
583
  python -m ruff check src tests
408
584
  python -m mypy src/proxycore
409
585
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "procproxy"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "ProcProxy —— 进程级代理:按进程决定哪条连接走哪个上游代理"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"