Skip to main content

waypoint

Turn client-side reachability measurements into a reviewable plan for which users move to which front when one gets blocked. Pure Python, zero dependencies.

It is a decision tool, not a proxy and not a panel. It runs no tunnel, serves no traffic, holds no obfuscation core, and generates no load. It reads an aggregated reachability summary and emits a plan — a list of recommended rotations with reasons and confidence — that the operator reads and applies deliberately. It is the "hands" to sniprobe's "eyes."

$ waypoint plan --endpoints fleet.json --reachability agg.json

Recommended rotations (1):
  AS 12389: move A -> S  [confirmed]  front a.example.com confirmed interfered on AS 12389 (interfered>=3, clear=0); moving cohort to clear standby s.example.com

Warnings (1):
  - no clear standby for AS 8359; users of B left in place

Decide, don't act

The engine emits a plan; applying it is a separate, explicit step behind an actuator boundary. The only actuator shipped here is a dry run — it renders what would happen and touches nothing.

The reason is a safety property. A bug in the decision logic must produce a wrong recommendation the operator can inspect and reject, never an automatic reconfiguration that breaks everyone's access at once. There is no flag in this version that touches real infrastructure; --apply runs the dry-run actuator and says so loudly.

What it does not rebuild

The circumvention ecosystem already solves, well and audited, everything but one thing. waypoint rebuilds none of it:

  • The tunnel and obfuscation core — Xray / REALITY do that.
  • One-click server install — existing installers (Amnezia and others) do that.
  • SNI selection from a server vantage — Hiddify-Reality-Scanner, RealityChecker and others do that.
  • Per-user config / subscription / QR / revocation — panels like 3x-ui and Marzban do that.

The gap none of them fill is the one this fills: consume client-side reachability and decide, per operator, when to rotate a front or activate a warm standby, and for whom. Until there was an input to decide on, the gap could not be filled.

Input: an aggregate, never raw reports

The engine consumes the aggregated, k-anonymity-thresholded reachability summary that sniprobe aggregate --json produces — keyed by candidate-SNI × AS, never raw per-volunteer reports. It never handles a volunteer's identity or address, and never handles a user list either: it decides at endpoint × operator-AS granularity ("for users of endpoint E on AS 12389, move to standby S"), and which real users that is stays local to the operator.

waypoint does not import sniprobe; it consumes the documented JSON shape:

{
  "networks": [8359, 12389],
  "candidates": {
    "front.example.com": {
      "overall": "interfered",
      "totals": {"clear": 0, "interfered": 2, "unreachable": 0, "inconclusive": 0},
      "by_network": {
        "8359":  {"clear": 3, "interfered": 0, "unreachable": 0, "inconclusive": 0},
        "12389": {"clear": 0, "interfered": 3, "unreachable": 0, "inconclusive": 0}
      },
      "suppressed_networks": []
    }
  }
}

A cell absent from by_network — suppressed by k-anonymity, or simply never observed — means no usable data for that (sni, AS). The engine treats it as inconclusive and never acts on it.

The endpoint inventory is a JSON list (or {"endpoints": [...]}):

[
  {"id": "A", "sni": "a.example.com", "role": "active"},
  {"id": "S", "sni": "s.example.com", "role": "standby", "note": "warm"}
]

The rules the engine enforces

  • Hysteresis / no-flap. It recommends a switch only on confirmed, sustained interference — at least --min-reports interfered observations with no clear observation in that cell. A mixed cell (any clear) is ambiguous and never actionable. Given an optional list of recent changes it will not re-switch a front it just moved.
  • Smallest blast radius. An action targets exactly one (endpoint, AS) cohort. A front blocked on one operator and clear on three triggers a move only for the blocked one — never "rotate everyone because one operator changed." Only real, numeric ASes are acted on; sniprobe's "unknown" catch-all bucket (unresolved operators) is never actionable and surfaces as a warning.
  • Fail-safe. It moves a cohort only onto a standby that is confirmed clear on that AS — meaning interfered == 0, clear >= --min-reports, and clear is the dominant non-interfered outcome (clear >= unreachable + inconclusive). A standby that clears the threshold but is mostly unreachable is a worse front, not a safe one, and is refused. If no confirmed-clear standby exists it emits a warning, not an action — churning users from one blocked front onto a blocked or unreachable one is worse than leaving them.
  • Diversity floor. It refuses to collapse the fleet onto a single SNI: a move that would drop the number of distinct in-service SNIs on an AS below --diversity-floor is held back, and when several fronts are blocked at once it spreads their cohorts across distinct standbys rather than funnelling them onto one.
  • No load, ever. Its only outputs are rotation recommendations for the operator's own fronts. There is no code path that recommends generating traffic or acting against the filter.

Output is deterministic — actions are sorted by AS then endpoint id — so a reviewer, and a test, can pin the plan exactly.

The actuator is a boundary; the panel adapter is yours

A real adapter — one that talks to 3x-ui, Marzban, or another panel to activate a standby or move a cohort — is a separate, operator-supplied integration and is deliberately not shipped here. It is the operator's own infrastructure and choice, and those panel API surfaces must not be guessed into the core. Implement the Actuator contract (src/waypoint/actuator.py) against your own panel if and when you want to apply plans; the engine's correctness does not depend on any panel.

Install

Zero runtime dependencies; Python 3.8+.

pip install waypoint-sni          # once published
waypoint plan --endpoints fleet.json --reachability agg.json

--json emits the machine-readable plan. --min-reports and --diversity-floor tune the two thresholds above.

Development

git clone https://github.com/Canavalny/waypoint
cd waypoint
python3 -m pytest -q          # 44 tests, no dependencies beyond pytest

The engine is a pure function, so every rule above is tested offline against constructed reachability summaries — no network, no clock, no panel.

Applying a plan: the Marzban adapter

The engine only decides. waypoint.marzban.MarzbanActuator is the first real adapter that applies — it talks to a Marzban panel to make a plan's standby fronts available. It authenticates, reads the panel's hosts, and adds a host for each standby SNI a plan wants to move users onto (GET/PUT /api/hosts). It never removes a host, and it defaults to a previewable, operator-in-the-loop flow.

One honest limit is built in: Marzban serves every user one subscription, so there is no per-AS-cohort steering to automate — and waypoint never holds the volunteer→AS mapping that would be needed anyway (privacy). The adapter does the part it can (ensure the standby is available) and emits a precise operator worklist for the part it cannot (move the specific cohort). It is a library integration you wire to your own panel and credentials; the CLI stays dry-run.

Verified how far: the adapter's request-building and response-parsing are unit-tested against a fake transport (auth, ensure-if-missing, idempotence, never-remove, worklist, warnings). It has not been run against a live Marzban panel, so treat live-API compatibility as unverified until you point it at your own instance.

Optional before publishing to PyPI: decide whether to fill in authors in pyproject.toml (currently commented out) and the LICENSE copyright line (currently "waypoint contributors"). The repository URLs are already set.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

waypoint_sni-0.1.0.tar.gz (26.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

waypoint_sni-0.1.0-py3-none-any.whl (19.9 kB view details)

Uploaded Python 3

File details

Details for the file waypoint_sni-0.1.0.tar.gz.

File metadata

  • Download URL: waypoint_sni-0.1.0.tar.gz
  • Upload date:
  • Size: 26.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for waypoint_sni-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7d0c7ea4441593b386cb85211383805278ac12fded5490997fcc903daf0b87a5
MD5 f1655751badf55bc6634ea7b0d522c42
BLAKE2b-256 0854363a94eb973d77cbce627c46ccd2b2d5f73b35eac3a0bfeb96f62f44e6f6

See more details on using hashes here.

Provenance

The following attestation bundles were made for waypoint_sni-0.1.0.tar.gz:

Publisher: release.yml on Canavalny/waypoint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file waypoint_sni-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: waypoint_sni-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for waypoint_sni-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8365a3dff6b9c6d6217f9d48f477c32fe31b91f904af85d8e4d47f606ac35e4c
MD5 09909aa8200bedeae9cf9b09419b108e
BLAKE2b-256 4cf94e198d15d72035b20bde742034437c8838bd3ee6b930880c3b40f18013fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for waypoint_sni-0.1.0-py3-none-any.whl:

Publisher: release.yml on Canavalny/waypoint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page