evo-cli 0.32.0__tar.gz → 0.32.1__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.
- {evo_cli-0.32.0 → evo_cli-0.32.1}/PKG-INFO +1 -1
- evo_cli-0.32.1/evo_cli/VERSION +1 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/_probe.py +22 -9
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/check.py +5 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/fix.py +1 -1
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/probe.py +3 -3
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli.egg-info/PKG-INFO +1 -1
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_route.py +46 -0
- evo_cli-0.32.0/evo_cli/VERSION +0 -1
- {evo_cli-0.32.0 → evo_cli-0.32.1}/Containerfile +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/HISTORY.md +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/LICENSE +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/MANIFEST.in +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/README.md +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/__main__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/base.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/cli.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/agent_toy.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/claude_code.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/cloudflare.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/cred.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/download.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/fix_claude.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/gdrive.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/gh.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_dag.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_git.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_hub.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_mirror.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_model.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_mutate.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_paths.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_render.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/_server.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/check.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/clone.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/edit.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/pull.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/serve.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/show.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/views.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/index-Cs7hXRPJ.css +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/index-CuNOlaH7.js +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/harness/web/index.html +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/hwid.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/hwid_reset.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/image.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/localproxy.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/mcp.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/miniconda.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/netcheck.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/ngrok.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/opencode.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/openvpn.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/plantuml.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/_dns.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/_dpi.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/_hosts.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/_spoofdpi.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/route/status.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/serp.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/site2s.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/ssh.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/storage.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/sysmon.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/tts.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/update.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/commands/wifi.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/console.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/doctor.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/google_oauth.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/migrate.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/oauth_flow.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/registry.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/store.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/credentials/sync.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/imaging/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/imaging/align.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/imaging/core.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/imaging/creds.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/imaging/errors.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/imaging/gemini.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/imaging/ncnn.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/mcp_registry.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/openvpn.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/serp/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/serp/client.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/serp/creds.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/serp/errors.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/serp/install.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/serp/render.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/chunking.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/core.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/creds.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/errors.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/gemini.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/http.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/openai.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/player.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli/tts/vbee.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli.egg-info/SOURCES.txt +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli.egg-info/dependency_links.txt +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli.egg-info/entry_points.txt +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli.egg-info/requires.txt +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/evo_cli.egg-info/top_level.txt +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/pyproject.toml +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/setup.cfg +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/__init__.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/fake_evo_agents.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/fixtures/hub/contract-0.2.0.json +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/fixtures/hub/mirror-demo.yaml +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_agent_toy.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_claude_code.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_cli.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_console.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_cred.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_download.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_fix_claude.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_gdrive.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_gh.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_harness.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_harness_clone.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_harness_dag.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_harness_hub.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_harness_show.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_image.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_mcp.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_ngrok.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_opencode.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_openvpn.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_plantuml.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_serp.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_storage.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_sysmon.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_tts.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_update.py +0 -0
- {evo_cli-0.32.0 → evo_cli-0.32.1}/tests/test_wifi.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.32.1
|
|
@@ -13,6 +13,7 @@ CONTROL_SNI = "example.com"
|
|
|
13
13
|
|
|
14
14
|
OPEN = "open"
|
|
15
15
|
DNS_POISONED = "dns-poisoned"
|
|
16
|
+
ADDRESS_BLOCKED = "address-blocked"
|
|
16
17
|
SNI_BLOCKED = "sni-blocked"
|
|
17
18
|
IP_BLOCKED = "ip-blocked"
|
|
18
19
|
UNREACHABLE = "unreachable"
|
|
@@ -20,6 +21,7 @@ UNREACHABLE = "unreachable"
|
|
|
20
21
|
VERDICT_TEXT = {
|
|
21
22
|
OPEN: "reachable - nothing in the way",
|
|
22
23
|
DNS_POISONED: "DNS is poisoned, the route itself is open",
|
|
24
|
+
ADDRESS_BLOCKED: "the address your system resolves is blocked, another one works",
|
|
23
25
|
SNI_BLOCKED: "DPI blocks the TLS handshake by server name",
|
|
24
26
|
IP_BLOCKED: "the address itself is blocked or dead",
|
|
25
27
|
UNREACHABLE: "no address answers at all",
|
|
@@ -103,24 +105,32 @@ def rank(host, candidates, timeout=6.0):
|
|
|
103
105
|
return rows
|
|
104
106
|
|
|
105
107
|
|
|
108
|
+
def candidates(answers):
|
|
109
|
+
"""The system's own addresses first, since those are what a browser connects to, then every trusted one."""
|
|
110
|
+
system = [ip for ip in answers["system"] or [] if not _dns.is_bogus(ip)]
|
|
111
|
+
return system + [ip for ip in answers["trusted"] if ip not in system]
|
|
112
|
+
|
|
113
|
+
|
|
106
114
|
def diagnose(host, timeout=6.0, include_vn=True):
|
|
107
115
|
answers = _dns.gather(host, include_vn=include_vn)
|
|
108
116
|
system_ips = answers["system"] or []
|
|
109
117
|
trusted = answers["trusted"]
|
|
110
118
|
|
|
111
|
-
|
|
119
|
+
# A name the system cannot resolve at all, while trusted resolvers can, is as dead to a browser as a bogus one.
|
|
120
|
+
poisoned = (bool(system_ips) and all(_dns.is_bogus(ip) for ip in system_ips)) or (not system_ips and bool(trusted))
|
|
112
121
|
if not poisoned and system_ips and trusted:
|
|
113
122
|
poisoned = not set(system_ips) & set(trusted) and any(_dns.is_bogus(ip) for ip in system_ips)
|
|
114
123
|
|
|
115
|
-
|
|
116
|
-
probes = rank(host,
|
|
124
|
+
addresses = candidates(answers)
|
|
125
|
+
probes = rank(host, addresses, timeout=timeout) if addresses else []
|
|
117
126
|
working = [row for row in probes if row["tls"]]
|
|
127
|
+
system_ok = any(row["ip"] in system_ips for row in working)
|
|
118
128
|
|
|
119
129
|
control = None
|
|
120
|
-
if
|
|
121
|
-
control = tls_probe(
|
|
130
|
+
if addresses and not working:
|
|
131
|
+
control = tls_probe(addresses[0], CONTROL_SNI, timeout=timeout)
|
|
122
132
|
|
|
123
|
-
verdict = _verdict(
|
|
133
|
+
verdict = _verdict(addresses, probes, working, control, poisoned, system_ok)
|
|
124
134
|
|
|
125
135
|
return {
|
|
126
136
|
"host": host,
|
|
@@ -128,7 +138,7 @@ def diagnose(host, timeout=6.0, include_vn=True):
|
|
|
128
138
|
"poisoned": poisoned,
|
|
129
139
|
"system_ips": system_ips,
|
|
130
140
|
"answers": answers["resolvers"],
|
|
131
|
-
"candidates":
|
|
141
|
+
"candidates": addresses,
|
|
132
142
|
"probes": probes,
|
|
133
143
|
"working": working,
|
|
134
144
|
"control": control,
|
|
@@ -136,11 +146,14 @@ def diagnose(host, timeout=6.0, include_vn=True):
|
|
|
136
146
|
}
|
|
137
147
|
|
|
138
148
|
|
|
139
|
-
def _verdict(candidates, probes, working, control, poisoned):
|
|
149
|
+
def _verdict(candidates, probes, working, control, poisoned, system_ok=True):
|
|
140
150
|
if not candidates:
|
|
141
151
|
return UNREACHABLE
|
|
142
152
|
if working:
|
|
143
|
-
|
|
153
|
+
if poisoned:
|
|
154
|
+
return DNS_POISONED
|
|
155
|
+
# Some address works, but not the one the system hands the browser: the name only opens once it is pinned.
|
|
156
|
+
return OPEN if system_ok else ADDRESS_BLOCKED
|
|
144
157
|
if control and control["tls"]:
|
|
145
158
|
return SNI_BLOCKED
|
|
146
159
|
if any(row["tcp"] for row in probes):
|
|
@@ -24,6 +24,7 @@ EPILOG = Text.from_markup(
|
|
|
24
24
|
VERDICT_STYLE = {
|
|
25
25
|
_probe.OPEN: "success",
|
|
26
26
|
_probe.DNS_POISONED: "warning",
|
|
27
|
+
_probe.ADDRESS_BLOCKED: "warning",
|
|
27
28
|
_probe.SNI_BLOCKED: "error",
|
|
28
29
|
_probe.IP_BLOCKED: "error",
|
|
29
30
|
_probe.UNREACHABLE: "error",
|
|
@@ -32,6 +33,7 @@ VERDICT_STYLE = {
|
|
|
32
33
|
ADVICE = {
|
|
33
34
|
_probe.OPEN: "Nothing to do.",
|
|
34
35
|
_probe.DNS_POISONED: "Run `evo route open {host}` to pin the working address, or fix DNS at the source.",
|
|
36
|
+
_probe.ADDRESS_BLOCKED: "Run `evo route open {host}` to pin the working address.",
|
|
35
37
|
_probe.SNI_BLOCKED: "hosts and DNS cannot help here. Run `evo route open {host}` to bring up the DPI bypass.",
|
|
36
38
|
_probe.IP_BLOCKED: "The address answers TCP but nothing completes - you need a tunnel or VPN.",
|
|
37
39
|
_probe.UNREACHABLE: "No address resolved or answered. Check the name, then your link.",
|
|
@@ -92,6 +94,9 @@ def render(report):
|
|
|
92
94
|
success(f"{host} is reachable. {message}")
|
|
93
95
|
elif verdict == _probe.DNS_POISONED:
|
|
94
96
|
warning(f"DNS hands back a bogus address for {host}, but {report['best']} works. {message}")
|
|
97
|
+
elif verdict == _probe.ADDRESS_BLOCKED:
|
|
98
|
+
dead = ", ".join(report["system_ips"])
|
|
99
|
+
warning(f"{host} resolves to {dead}, which is blocked, but {report['best']} works. {message}")
|
|
95
100
|
else:
|
|
96
101
|
error(f"{host}: {_probe.VERDICT_TEXT[verdict]}. {message}")
|
|
97
102
|
|
|
@@ -130,7 +130,7 @@ def open_route(host, method, dry_run, timeout):
|
|
|
130
130
|
if verdict == _probe.OPEN:
|
|
131
131
|
success(f"{host} is already reachable - nothing to do.")
|
|
132
132
|
return report
|
|
133
|
-
if verdict
|
|
133
|
+
if verdict in (_probe.DNS_POISONED, _probe.ADDRESS_BLOCKED):
|
|
134
134
|
method = "hosts"
|
|
135
135
|
elif verdict == _probe.SNI_BLOCKED:
|
|
136
136
|
method = "dpi"
|
|
@@ -37,7 +37,7 @@ def probe(host, timeout, show_all, no_vn, as_json):
|
|
|
37
37
|
with console.status(f"[info]collecting addresses for {host}...[/info]", spinner="dots"):
|
|
38
38
|
answers = _dns.gather(host, include_vn=not no_vn)
|
|
39
39
|
|
|
40
|
-
candidates =
|
|
40
|
+
candidates = _probe.candidates(answers)
|
|
41
41
|
if not candidates:
|
|
42
42
|
error(f"No usable address for {host}.")
|
|
43
43
|
sys.exit(1)
|
|
@@ -61,13 +61,13 @@ def probe(host, timeout, show_all, no_vn, as_json):
|
|
|
61
61
|
table.add_column("time")
|
|
62
62
|
table.add_column("seen from")
|
|
63
63
|
|
|
64
|
-
origin = _origin_map(answers["resolvers"])
|
|
64
|
+
origin = _origin_map({"system": answers["system"], **answers["resolvers"]})
|
|
65
65
|
for row in shown:
|
|
66
66
|
table.add_row(
|
|
67
67
|
row["ip"],
|
|
68
68
|
"[success]ok[/success]" if row["tls"] else "[error]blocked[/error]",
|
|
69
69
|
f"{row['ms']:.0f}ms" if row["ms"] is not None else "-",
|
|
70
|
-
", ".join(origin.get(row["ip"], []))
|
|
70
|
+
", ".join(origin.get(row["ip"], [])),
|
|
71
71
|
)
|
|
72
72
|
console.print()
|
|
73
73
|
console.print(table)
|
|
@@ -123,6 +123,52 @@ def test_verdict_unreachable_when_tcp_never_opens():
|
|
|
123
123
|
assert _probe._verdict(["1.2.3.4"], probes, [], control, False) == _probe.UNREACHABLE
|
|
124
124
|
|
|
125
125
|
|
|
126
|
+
def test_verdict_address_blocked_when_only_a_foreign_address_works():
|
|
127
|
+
probes = [_probe_row("5.6.7.8", tls=True), _probe_row("1.2.3.4", tcp=False)]
|
|
128
|
+
assert _probe._verdict(["1.2.3.4", "5.6.7.8"], probes, probes[:1], None, False, False) == _probe.ADDRESS_BLOCKED
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _fake_network(monkeypatch, system, trusted, live):
|
|
132
|
+
"""Resolvers answer with ``system`` and ``trusted``; only the addresses in ``live`` finish a handshake."""
|
|
133
|
+
answers = {"system": system, "resolvers": {"doh": trusted}, "trusted": trusted}
|
|
134
|
+
monkeypatch.setattr(_dns, "gather", lambda host, include_vn=True: answers)
|
|
135
|
+
tried = []
|
|
136
|
+
|
|
137
|
+
def tls_probe(ip, sni, timeout=6.0):
|
|
138
|
+
tried.append(ip)
|
|
139
|
+
return _probe_row(ip, tcp=ip in live, tls=ip in live)
|
|
140
|
+
|
|
141
|
+
monkeypatch.setattr(_probe, "tls_probe", tls_probe)
|
|
142
|
+
return tried
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_diagnose_probes_the_address_the_system_uses(monkeypatch):
|
|
146
|
+
# www.facebook.com, 2026-10: the system answer is a real Meta address that the network drops,
|
|
147
|
+
# while the one DoH hands out works. Testing only the DoH address read that as open.
|
|
148
|
+
tried = _fake_network(monkeypatch, ["57.144.64.1"], ["157.240.13.35"], {"157.240.13.35"})
|
|
149
|
+
report = _probe.diagnose("www.facebook.com")
|
|
150
|
+
assert "57.144.64.1" in tried
|
|
151
|
+
assert report["verdict"] == _probe.ADDRESS_BLOCKED
|
|
152
|
+
assert report["best"] == "157.240.13.35"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_diagnose_open_when_the_system_address_works(monkeypatch):
|
|
156
|
+
_fake_network(monkeypatch, ["1.2.3.4"], ["5.6.7.8"], {"1.2.3.4", "5.6.7.8"})
|
|
157
|
+
assert _probe.diagnose("example.org")["verdict"] == _probe.OPEN
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_diagnose_without_a_system_answer_needs_a_pin(monkeypatch):
|
|
161
|
+
_fake_network(monkeypatch, None, ["5.6.7.8"], {"5.6.7.8"})
|
|
162
|
+
assert _probe.diagnose("example.org")["verdict"] == _probe.DNS_POISONED
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_open_pins_a_host_whose_system_address_is_blocked(monkeypatch):
|
|
166
|
+
_fake_network(monkeypatch, ["57.144.64.1"], ["157.240.13.35"], {"157.240.13.35"})
|
|
167
|
+
result = CliRunner().invoke(cli, ["route", "open", "www.facebook.com", "--dry-run"])
|
|
168
|
+
assert result.exit_code == 0, result.output
|
|
169
|
+
assert "Would pin" in result.output and "157.240.13.35" in result.output
|
|
170
|
+
|
|
171
|
+
|
|
126
172
|
HOSTS_SAMPLE = "127.0.0.1 localhost\n192.168.1.9 nas.local\n"
|
|
127
173
|
|
|
128
174
|
|
evo_cli-0.32.0/evo_cli/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.32.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|