procproxy 0.1.0__py3-none-any.whl

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 (45) hide show
  1. procproxy-0.1.0.dist-info/METADATA +489 -0
  2. procproxy-0.1.0.dist-info/RECORD +45 -0
  3. procproxy-0.1.0.dist-info/WHEEL +5 -0
  4. procproxy-0.1.0.dist-info/entry_points.txt +2 -0
  5. procproxy-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. procproxy-0.1.0.dist-info/top_level.txt +1 -0
  7. proxycore/__init__.py +7 -0
  8. proxycore/__main__.py +65 -0
  9. proxycore/api.py +226 -0
  10. proxycore/backend/__init__.py +49 -0
  11. proxycore/backend/base.py +159 -0
  12. proxycore/backend/null.py +51 -0
  13. proxycore/backend/windivert/__init__.py +657 -0
  14. proxycore/backend/windivert/filter.py +123 -0
  15. proxycore/backend/windivert/pidmap.py +289 -0
  16. proxycore/backend/windivert/pkt.py +424 -0
  17. proxycore/canproxy.py +229 -0
  18. proxycore/checkroute.py +247 -0
  19. proxycore/cli.py +605 -0
  20. proxycore/config.py +1010 -0
  21. proxycore/dns/__init__.py +46 -0
  22. proxycore/dns/cache.py +209 -0
  23. proxycore/dns/modes.py +451 -0
  24. proxycore/dns/udp.py +606 -0
  25. proxycore/forward.py +572 -0
  26. proxycore/ipc.py +637 -0
  27. proxycore/log.py +298 -0
  28. proxycore/pathsetup.py +112 -0
  29. proxycore/probe.py +168 -0
  30. proxycore/proc.py +342 -0
  31. proxycore/rules.py +730 -0
  32. proxycore/runtime.py +654 -0
  33. proxycore/service/__init__.py +536 -0
  34. proxycore/service/__main__.py +124 -0
  35. proxycore/tunnel/__init__.py +8 -0
  36. proxycore/tunnel/relay.py +393 -0
  37. proxycore/tunnel/sessions.py +315 -0
  38. proxycore/tunnel/sink.py +325 -0
  39. proxycore/types.py +165 -0
  40. proxycore/upstream/__init__.py +52 -0
  41. proxycore/upstream/base.py +150 -0
  42. proxycore/upstream/chain.py +92 -0
  43. proxycore/upstream/http_connect.py +148 -0
  44. proxycore/upstream/socks4.py +106 -0
  45. proxycore/upstream/socks5.py +266 -0
@@ -0,0 +1,46 @@
1
+ """DNS 与 UDP 处理。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .cache import DEFAULT_TTL_S, CacheEntry, DnsCache, on_network_change
6
+ from .modes import (
7
+ MODE_BYPASS,
8
+ MODE_LOCAL,
9
+ MODE_REMOTE,
10
+ DnsPolicy,
11
+ ResolutionPlan,
12
+ build_query,
13
+ parse_answers,
14
+ query_a_record,
15
+ sniff_queries,
16
+ )
17
+ from .udp import (
18
+ MAX_UDP_DATAGRAM,
19
+ UdpForwarder,
20
+ UdpPolicyResolver,
21
+ UdpRelayTransport,
22
+ UdpSession,
23
+ UdpSessionTable,
24
+ )
25
+
26
+ __all__ = [
27
+ "DEFAULT_TTL_S",
28
+ "MAX_UDP_DATAGRAM",
29
+ "MODE_BYPASS",
30
+ "MODE_LOCAL",
31
+ "MODE_REMOTE",
32
+ "CacheEntry",
33
+ "DnsCache",
34
+ "DnsPolicy",
35
+ "ResolutionPlan",
36
+ "UdpForwarder",
37
+ "UdpPolicyResolver",
38
+ "UdpRelayTransport",
39
+ "UdpSession",
40
+ "UdpSessionTable",
41
+ "build_query",
42
+ "on_network_change",
43
+ "parse_answers",
44
+ "query_a_record",
45
+ "sniff_queries",
46
+ ]
proxycore/dns/cache.py ADDED
@@ -0,0 +1,209 @@
1
+ """DNS 缓存:IP ↔ 域名映射,带 TTL 且必须标记 inferred。
2
+
3
+ 红线:
4
+ * 通过 DNS 嗅探建立的映射只是近似(TTL 内有效,CDN 会误判);
5
+ * 必须带 TTL、必须标记 `inferred=True`、日志里必须能打出 `reason=name-inferred`;
6
+ * 不得用于 BLOCK 决策(`is_trusted()` 只对"确定的域名"返回 True)。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import threading
12
+ import time
13
+ from dataclasses import dataclass
14
+ from typing import Any
15
+
16
+ DEFAULT_TTL_S = 300.0
17
+ MAX_ENTRIES = 20000
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class CacheEntry:
22
+ addr: str
23
+ name: str
24
+ expires_at: float
25
+ inferred: bool = True # 嗅探推断出来的 → 近似匹配
26
+ source: str = "dns-sniff"
27
+
28
+ def to_dict(self) -> dict[str, Any]:
29
+ return {
30
+ "addr": self.addr,
31
+ "name": self.name,
32
+ "expires_at": self.expires_at,
33
+ "inferred": self.inferred,
34
+ "source": self.source,
35
+ }
36
+
37
+
38
+ class DnsCache:
39
+ """线程安全的 IP→域名表(`dict` + TTL 惰性清理)。"""
40
+
41
+ def __init__(self, *, default_ttl_s: float = DEFAULT_TTL_S, max_entries: int = MAX_ENTRIES,
42
+ clock: Any = time.time) -> None:
43
+ self.default_ttl_s = float(default_ttl_s)
44
+ self.max_entries = max_entries
45
+ self._clock = clock
46
+ self._lock = threading.RLock()
47
+ self._by_ip: dict[str, CacheEntry] = {}
48
+ self._flush_count = 0
49
+ # 每次内容变化 +1。`GuardSet.inferred_names` 需要跟着缓存走,但
50
+ # `snapshot()` 是 O(条目数)(上限 20000),不能在决策热路径上每秒重算一遍;
51
+ # 调用方比对这个数就知道"要不要刷新"。
52
+ self._revision = 0
53
+
54
+ # ------------------------------------------------------------ 写入 -----
55
+ def put(
56
+ self,
57
+ addr: str,
58
+ name: str,
59
+ *,
60
+ ttl_s: float | None = None,
61
+ inferred: bool = True,
62
+ source: str = "dns-sniff",
63
+ ) -> CacheEntry:
64
+ ttl = self.default_ttl_s if ttl_s is None else max(1.0, float(ttl_s))
65
+ entry = CacheEntry(
66
+ addr=addr,
67
+ name=name,
68
+ expires_at=self._clock() + ttl,
69
+ inferred=inferred,
70
+ source=source,
71
+ )
72
+ with self._lock:
73
+ self._by_ip[addr] = entry
74
+ self._revision += 1
75
+ if len(self._by_ip) > self.max_entries:
76
+ self._evict_locked()
77
+ return entry
78
+
79
+ # ------------------------------------------------------------ 读取 -----
80
+ def get(self, addr: str) -> str | None:
81
+ """取域名(过期即删)。"""
82
+ with self._lock:
83
+ entry = self._by_ip.get(addr)
84
+ if entry is None:
85
+ return None
86
+ if entry.expires_at <= self._clock():
87
+ del self._by_ip[addr]
88
+ self._revision += 1 # 让 inferred_names 的持有者知道内容变了
89
+ return None
90
+ return entry.name
91
+
92
+ def entry(self, addr: str) -> CacheEntry | None:
93
+ with self._lock:
94
+ entry = self._by_ip.get(addr)
95
+ if entry is None or entry.expires_at <= self._clock():
96
+ if self._by_ip.pop(addr, None) is not None:
97
+ self._revision += 1
98
+ return None
99
+ return entry
100
+
101
+ def is_inferred(self, addr: str) -> bool:
102
+ entry = self.entry(addr)
103
+ return bool(entry and entry.inferred)
104
+
105
+ def is_trusted(self, addr: str) -> bool:
106
+ """只有"确定的域名"才可信;推断出来的映射不可用于 BLOCK 决策。"""
107
+ entry = self.entry(addr)
108
+ return bool(entry and not entry.inferred)
109
+
110
+ def resolve(self, addr: str) -> str | None:
111
+ """`GuardSet.name_resolver` 的签名。"""
112
+ return self.get(addr)
113
+
114
+ def __len__(self) -> int:
115
+ with self._lock:
116
+ self._purge_locked()
117
+ return len(self._by_ip)
118
+
119
+ def snapshot(self) -> list[dict[str, Any]]:
120
+ with self._lock:
121
+ self._purge_locked()
122
+ return [e.to_dict() for e in self._by_ip.values()]
123
+
124
+ def stats(self) -> dict[str, Any]:
125
+ with self._lock:
126
+ self._purge_locked()
127
+ inferred = sum(1 for e in self._by_ip.values() if e.inferred)
128
+ return {
129
+ "entries": len(self._by_ip),
130
+ "inferred": inferred,
131
+ "flushes": self._flush_count,
132
+ }
133
+
134
+ # ------------------------------------------------------------ 清理 -----
135
+ def clear_inferred(self) -> int:
136
+ """网络切换时清掉推断映射。"""
137
+ with self._lock:
138
+ victims = [addr for addr, e in self._by_ip.items() if e.inferred]
139
+ for addr in victims:
140
+ del self._by_ip[addr]
141
+ self._flush_count += 1
142
+ self._revision += 1
143
+ return len(victims)
144
+
145
+ def clear(self) -> int:
146
+ with self._lock:
147
+ count = len(self._by_ip)
148
+ self._by_ip.clear()
149
+ self._flush_count += 1
150
+ self._revision += 1
151
+ return count
152
+
153
+ # -------------------------------------------------------- 变更通知 -----
154
+ @property
155
+ def revision(self) -> int:
156
+ """内容版本号;调用方据此判断"要不要重新读一遍缓存"。"""
157
+ with self._lock:
158
+ return self._revision
159
+
160
+ def inferred_names(self) -> set[str]:
161
+ """当前未过期的推断域名集合(`GuardSet.inferred_names` 的来源)。"""
162
+ with self._lock:
163
+ self._purge_locked()
164
+ return {e.name for e in self._by_ip.values() if e.inferred}
165
+
166
+ def _purge_locked(self) -> int:
167
+ now = self._clock()
168
+ victims = [addr for addr, e in self._by_ip.items() if e.expires_at <= now]
169
+ for addr in victims:
170
+ del self._by_ip[addr]
171
+ if victims:
172
+ self._revision += 1
173
+ return len(victims)
174
+
175
+ def _evict_locked(self) -> None:
176
+ # 先清过期,再按即将到期顺序淘汰
177
+ self._purge_locked()
178
+ overflow = len(self._by_ip) - self.max_entries
179
+ if overflow <= 0:
180
+ return
181
+ for addr, _ in sorted(self._by_ip.items(), key=lambda kv: kv[1].expires_at)[:overflow]:
182
+ del self._by_ip[addr]
183
+ self._revision += 1
184
+
185
+
186
+ def on_network_change(cache: DnsCache, *, flush_system: bool = False) -> dict[str, Any]:
187
+ """网络接口变化时的处理(等效于 `ipconfig /flushdns`)。"""
188
+ import subprocess
189
+
190
+ removed = cache.clear_inferred()
191
+ result: dict[str, Any] = {"cleared_inferred": removed, "system_flush": False}
192
+ if flush_system:
193
+ from ..proc import IS_WINDOWS
194
+
195
+ cmd = ["ipconfig", "/flushdns"] if IS_WINDOWS else ["resolvectl", "flush-caches"]
196
+ try:
197
+ subprocess.run(cmd, capture_output=True, check=False, timeout=5)
198
+ result["system_flush"] = True
199
+ except (OSError, subprocess.SubprocessError) as exc:
200
+ result["system_flush_error"] = str(exc)
201
+ return result
202
+
203
+
204
+ __all__ = [
205
+ "DEFAULT_TTL_S",
206
+ "CacheEntry",
207
+ "DnsCache",
208
+ "on_network_change",
209
+ ]