psiphon 2.0.0__tar.gz → 2.0.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.
- {psiphon-2.0.0 → psiphon-2.0.2}/PKG-INFO +6 -1
- {psiphon-2.0.0 → psiphon-2.0.2}/README.md +5 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/pyproject.toml +1 -1
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/config.py +2 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/main.py +71 -13
- {psiphon-2.0.0 → psiphon-2.0.2}/LICENSE +0 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/__init__.py +0 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/assets/LICENSE.engine +0 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/assets/psiphon.config +0 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/engine.py +0 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/network.py +0 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/routing.py +0 -0
- {psiphon-2.0.0 → psiphon-2.0.2}/src/psiphon/supervisor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: psiphon
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Foreground native Psiphon packet tunnel and explicit local proxy for Linux
|
|
5
5
|
Author: KpihX
|
|
6
6
|
Author-email: KpihX <kapoivha@gmail.com>
|
|
@@ -69,6 +69,11 @@ the worker refuses before mutations. Cancellation starts no supervisor or route
|
|
|
69
69
|
For agent-operated privilege, launch connect only in an armed tmux pane in the
|
|
70
70
|
agent's current window. No sudo or privileged operation runs in offline checks.
|
|
71
71
|
|
|
72
|
+
Logs live in `$XDG_STATE_HOME/psiphon/psiphon.log` (default
|
|
73
|
+
`~/.local/state/psiphon/`). Before a new connection, a 2 MiB log rotates through
|
|
74
|
+
four numbered prior logs (`.1` to `.4`), bounding retained log storage to about
|
|
75
|
+
10 MiB. The oldest retained log is discarded on the next rotation.
|
|
76
|
+
|
|
72
77
|
**Escalation trust:** sudo approves this local Python interpreter and installed
|
|
73
78
|
package, including an editable checkout if that is what you installed. They may
|
|
74
79
|
be user-writable. `-I` avoids ambient PYTHONPATH/user-site, but does not make that
|
|
@@ -52,6 +52,11 @@ the worker refuses before mutations. Cancellation starts no supervisor or route
|
|
|
52
52
|
For agent-operated privilege, launch connect only in an armed tmux pane in the
|
|
53
53
|
agent's current window. No sudo or privileged operation runs in offline checks.
|
|
54
54
|
|
|
55
|
+
Logs live in `$XDG_STATE_HOME/psiphon/psiphon.log` (default
|
|
56
|
+
`~/.local/state/psiphon/`). Before a new connection, a 2 MiB log rotates through
|
|
57
|
+
four numbered prior logs (`.1` to `.4`), bounding retained log storage to about
|
|
58
|
+
10 MiB. The oldest retained log is discarded on the next rotation.
|
|
59
|
+
|
|
55
60
|
**Escalation trust:** sudo approves this local Python interpreter and installed
|
|
56
61
|
package, including an editable checkout if that is what you installed. They may
|
|
57
62
|
be user-writable. `-I` avoids ambient PYTHONPATH/user-site, but does not make that
|
|
@@ -50,6 +50,8 @@ RUNTIME_DIR = _runtime / f"psiphon-{os.getuid()}"
|
|
|
50
50
|
ACTIVE_CONFIG = CONFIG_DIR / "psiphon.config"
|
|
51
51
|
PID_FILE = RUNTIME_DIR / "psiphon.pid"
|
|
52
52
|
LOG_FILE = STATE_DIR / "psiphon.log"
|
|
53
|
+
LOG_MAX_BYTES = 2 * 1024 * 1024
|
|
54
|
+
LOG_BACKUPS = 4
|
|
53
55
|
SESSION_FILE = RUNTIME_DIR / "session.json"
|
|
54
56
|
DATA_DIR = CACHE_DIR / "core" # signed server-list cache and tunnel-core datastore
|
|
55
57
|
|
|
@@ -17,6 +17,7 @@ from typing import Annotated, Any
|
|
|
17
17
|
import requests
|
|
18
18
|
import typer
|
|
19
19
|
from rich.console import Console
|
|
20
|
+
from rich.table import Table
|
|
20
21
|
|
|
21
22
|
from psiphon import config, engine, network, routing
|
|
22
23
|
from psiphon.supervisor import RollbackError, Supervisor
|
|
@@ -112,6 +113,23 @@ def notice_state(offset: int = 0) -> dict[str, Any]:
|
|
|
112
113
|
return state
|
|
113
114
|
|
|
114
115
|
|
|
116
|
+
def rotate_log() -> None:
|
|
117
|
+
"""Keep a bounded local history before opening a new connection log."""
|
|
118
|
+
if (
|
|
119
|
+
not config.LOG_FILE.exists()
|
|
120
|
+
or config.LOG_FILE.stat().st_size < config.LOG_MAX_BYTES
|
|
121
|
+
):
|
|
122
|
+
return
|
|
123
|
+
oldest = config.LOG_FILE.with_name(f"{config.LOG_FILE.name}.{config.LOG_BACKUPS}")
|
|
124
|
+
if oldest.exists():
|
|
125
|
+
oldest.unlink()
|
|
126
|
+
for number in range(config.LOG_BACKUPS - 1, 0, -1):
|
|
127
|
+
source = config.LOG_FILE.with_name(f"{config.LOG_FILE.name}.{number}")
|
|
128
|
+
if source.exists():
|
|
129
|
+
source.replace(source.with_name(f"{config.LOG_FILE.name}.{number + 1}"))
|
|
130
|
+
config.LOG_FILE.replace(config.LOG_FILE.with_name(f"{config.LOG_FILE.name}.1"))
|
|
131
|
+
|
|
132
|
+
|
|
115
133
|
def process_identity(pid: int) -> dict[str, str]:
|
|
116
134
|
proc = Path(f"/proc/{pid}")
|
|
117
135
|
stat = (proc / "stat").read_text().rsplit(") ", 1)[1].split()
|
|
@@ -169,7 +187,8 @@ def connected(region: str, proxy: bool, interface: str | None, wait_s: float):
|
|
|
169
187
|
elif interface:
|
|
170
188
|
uplink = interface
|
|
171
189
|
engine.write_config(region, proxy)
|
|
172
|
-
# Create a private log before the worker opens it;
|
|
190
|
+
# Create a private log before the worker opens it; preserve bounded history.
|
|
191
|
+
rotate_log()
|
|
173
192
|
fd = os.open(
|
|
174
193
|
config.LOG_FILE,
|
|
175
194
|
os.O_APPEND | os.O_CREAT | os.O_WRONLY | os.O_NOFOLLOW,
|
|
@@ -341,12 +360,7 @@ def status(
|
|
|
341
360
|
"-p",
|
|
342
361
|
help="Measure explicit local proxy instead of active OS route",
|
|
343
362
|
),
|
|
344
|
-
geo: bool = typer.Option(
|
|
345
|
-
False,
|
|
346
|
-
"--geo",
|
|
347
|
-
"-g",
|
|
348
|
-
help="Query third-party GeoIP (not physical-location proof)",
|
|
349
|
-
),
|
|
363
|
+
geo: bool = typer.Option(False, "--geo", "-g", help="Query third-party GeoIP"),
|
|
350
364
|
download: bool = typer.Option(False, "--download", "-d"),
|
|
351
365
|
upload: bool = typer.Option(False, "--upload", "-u"),
|
|
352
366
|
speed: bool = typer.Option(
|
|
@@ -355,17 +369,61 @@ def status(
|
|
|
355
369
|
seconds: float = typer.Option(
|
|
356
370
|
config.BENCH_DEFAULT_S, "--seconds", "-n", min=1, max=60
|
|
357
371
|
),
|
|
372
|
+
output_format: str = typer.Option("table", "--format", "-f", help="table or json"),
|
|
358
373
|
) -> None:
|
|
359
374
|
"""Inspect the current route and HTTPS egress, even without Psiphon running."""
|
|
375
|
+
if output_format not in ("table", "json"):
|
|
376
|
+
raise typer.BadParameter("format must be table or json")
|
|
360
377
|
route = routing.active_route()
|
|
361
|
-
console.print("Active OS route: " + json.dumps(route), markup=False)
|
|
362
378
|
transport = proxy_url(proxy)
|
|
363
379
|
address = network.public_ip(transport)
|
|
364
|
-
|
|
365
|
-
f"{'Explicit proxy' if proxy else 'Active route'} HTTPS IP: {address}"
|
|
366
|
-
)
|
|
380
|
+
location = {}
|
|
367
381
|
if geo:
|
|
368
|
-
|
|
382
|
+
try:
|
|
383
|
+
location = network.geo(address, transport)
|
|
384
|
+
except (ConnectionError, requests.RequestException):
|
|
385
|
+
location = {}
|
|
386
|
+
state = {}
|
|
387
|
+
if config.SESSION_FILE.exists():
|
|
388
|
+
try:
|
|
389
|
+
state = json.loads(config.SESSION_FILE.read_text())
|
|
390
|
+
except (OSError, ValueError):
|
|
391
|
+
state = {"state": "unreadable"}
|
|
392
|
+
payload: dict[str, Any] = {
|
|
393
|
+
"mode": "explicit-proxy" if proxy else "active-route",
|
|
394
|
+
"interface": route.get("dev", ""),
|
|
395
|
+
"local_ipv4": route.get("prefsrc", route.get("src", "")),
|
|
396
|
+
"gateway": route.get("gateway", ""),
|
|
397
|
+
"public_ipv4": address,
|
|
398
|
+
"location": location,
|
|
399
|
+
"session": state,
|
|
400
|
+
}
|
|
401
|
+
if output_format == "json":
|
|
402
|
+
console.print_json(json.dumps(payload))
|
|
403
|
+
else:
|
|
404
|
+
table = Table(title="Current IPv4 Network Status", show_header=False)
|
|
405
|
+
table.add_column("Property", style="cyan")
|
|
406
|
+
table.add_column("Value")
|
|
407
|
+
table.add_row("Mode", payload["mode"])
|
|
408
|
+
table.add_row("Active interface", payload["interface"] or "not reported")
|
|
409
|
+
table.add_row("Local IPv4", payload["local_ipv4"] or "not reported")
|
|
410
|
+
table.add_row("Gateway", payload["gateway"] or "point-to-point or not reported")
|
|
411
|
+
table.add_row("Public IPv4", address)
|
|
412
|
+
country = " ".join(
|
|
413
|
+
str(value)
|
|
414
|
+
for value in (location.get("country", ""), location.get("country_code", ""))
|
|
415
|
+
if value
|
|
416
|
+
)
|
|
417
|
+
table.add_row(
|
|
418
|
+
"Location (indicative)",
|
|
419
|
+
", ".join(
|
|
420
|
+
value for value in (country, str(location.get("city", ""))) if value
|
|
421
|
+
)
|
|
422
|
+
or ("unavailable" if geo else "not requested; use -g"),
|
|
423
|
+
)
|
|
424
|
+
table.add_row("Requested region", str(state.get("region", "not connected")))
|
|
425
|
+
table.add_row("Tunnel mode", str(state.get("mode", "not connected")))
|
|
426
|
+
console.print(table)
|
|
369
427
|
for enabled, name, measure in (
|
|
370
428
|
(download or speed, "download", network.download),
|
|
371
429
|
(upload or speed, "upload", network.upload),
|
|
@@ -373,7 +431,7 @@ def status(
|
|
|
373
431
|
if enabled:
|
|
374
432
|
result = measure(seconds, transport)
|
|
375
433
|
console.print(
|
|
376
|
-
f"{name}: {result.mbps:.
|
|
434
|
+
f"[green]{name.title()}:[/green] {result.mbps:.2f} Mbps · {result.bytes_received / 1_000_000:.1f} MB in {result.elapsed_s:.1f}s"
|
|
377
435
|
)
|
|
378
436
|
|
|
379
437
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|