Repository navigation
router checks
Status: in progress in #84.
The router card on the Home page shows the four checks that eepview runs on the router. Each check shows what the backend found, and nothing else. A check turns green only when the backend reports that it passed. No timer and no CSS animation in the page changes a check.
Before this page, the card showed a path "You → Hop → Hop → Hop → Site". Its dots turned green when the router state was ok, but nothing checked a hop. The path is gone.
| # | id |
Name on the card | What eepview checks |
|---|---|---|---|
| 1 | proxy-i2p |
Proxy is an I2P router | VERIFY: GET http://proxy.i2p/ through the router proxy answers 200 with "I2P HTTP proxy OK", and the gatekeeper runs (ADR 0001, rule 5). |
| 2 | version |
Router version is supported | The router version is at least the minimum for its type: Java I2P 2.4.0, i2pd 2.50.0. |
| 3 | no-outproxy |
No outproxy | The HTTP proxy tunnel of the router, on the port of the router proxy, lists no outproxy. eepview reads the router's own tunnel configuration file. |
| 4 | tunnels |
Network up, client tunnel built | The router network status is OK, and the router has at least one client tunnel. |
Only check 1 opens and closes the gate. Checks 2 to 4 show a fact. They never close the gate and never change RouterStatus.state. The gatekeeper forwards only .i2p hosts (ADR 0001, layer L1), so a router outproxy cannot carry a request from eepview. An old router or a router with no tunnel yet cannot make eepview leak: pages only fail to load.
The minimum versions are the first releases of each router with the network database hardening of late 2023: Java I2P 2.4.0 (December 2023) and i2pd 2.50.0 (January 2024).
Each requirement is a test target. The tests check these rules, not the code.
-
V1 Shape.
RouterStatus.checksalways has exactly four entries, in the order of the table:proxy-i2p,version,no-outproxy,tunnels. Each entry is{id, state, detail, passedAt}. -
V2 States.
-
pending: the check has not started. It waits for check 1, or for the gate. -
running: the check runs now. -
passed: the last run passed. -
failed: the last run failed. -
not-checked: eepview cannot check this for this router.detailsays why. A check that eepview cannot run is neverpassed.
-
-
V3 Detail. A
failedornot-checkedcheck always has adetailthat is not empty: the short reason. Apassedcheck may have adetail: the fact that it checked. Apendingorrunningcheck hasdetailnull. -
V4 Pass time.
passedAtis the Unix time in ms when the check turnedpassed. It stays the same while the check stayspassed, round after round. In every other state it isnull. A check that leavespassedand passes again gets the time of the new pass.
-
V5 Start. At start, all four checks are
pending. When a VERIFY round starts and check 1 ispending, check 1 turnsrunning. A check 1 that ispassedorfailedkeeps that state while the round runs, so the card does not flicker every 5 s. -
V6 Result. After the round,
RouterStatus.stateokmakes check 1passed. Any other state makes itfailed, and itsdetailis theRouterStatus.detailof that round: the reason VERIFY gave, such as a refused connection or a wrong self-test page. When thatdetailisnull, the checkdetailis the router state. -
V7 Gate closed. While check 1 is not
passed, checks 2 to 4 arepending, withdetailandpassedAtnull. So when the router stops or a VERIFY round fails, check 1 showsfailedwith its reason, and checks 2 to 4 go back topending. -
V8 Pause and resume.
connection_pause()makes all four checkspending. While the connection is paused, they staypending.connection_resume()makes all fourpending. The next VERIFY round then runs as V5 and V6.
-
V9 When they run. When check 1 turns
passedand the connection is not paused, checks 2 to 4 turnrunning. In the same round, eepview runs them (V10 to V13). It runs them again in every VERIFY round (every 5 s) while the gate is open. -
V10 Facts. eepview reads these facts once per round. It reads them on a thread of their own, not on the watcher thread, so a slow console never delays the next VERIFY round. At most one such read runs at a time: a round that starts while the last read still runs skips checks 2 to 4.
- The router type: the type of the console that detection stored (Router console, R5): Java I2P or i2pd. With no stored console, the type is not known.
- The router version:
RouterStatus.version(from the router helper) when it is set, else the version of the stored console (R18). - The router statistics: from the router helper when it answers, else from the stored console, with the request of R32 and R33. With neither, there are none. This read adds no sample to the bandwidth history (R40 does not change).
- The outproxy configuration (V12).
-
V11 Check 2, the version.
- Router type not known:
not-checked. The detail says that no router console was found, so the router type is not known. - Version
null:not-checked. The detail says that the router did not report its version. - The version text is one to three decimal integers joined by
., then optionally-and any text, which is ignored. A missing part is0. So2.10.0-3is 2.10.0 and2.7is 2.7.0. Any other text:not-checked, and the detail quotes the text. - The version compares part by part, as numbers: 2.10.0 is newer than 2.9.0.
- At least the minimum of its type (Java I2P 2.4.0, i2pd 2.50.0):
passed. The detail names the version and the minimum. - Older:
failed. The detail names the version and the minimum.
- Router type not known:
-
V12 Check 3, no outproxy.
- The proxy port is the port of
RouterStatus.proxy(127.0.0.1:4444gives 4444). -
Java I2P. In each Java I2P configuration folder of R2, in that order: every file in
i2ptunnel.config.d/, by file name, theni2ptunnel.config. A file is a Java properties file (key=value;#and!start a comment line). Ini2ptunnel.config, the keys of tunnel<n>aretunnel.<n>.<key>. A file ini2ptunnel.config.d/holds one tunnel, and its keys are either<key>ortunnel.<n>.<key>. A tunnel is the HTTP proxy whentypeishttpclientandlistenPortis the proxy port. Its outproxies are the values ofproxyListand ofoption.i2ptunnel.httpclient.SSLOutproxies, split on,,;and white space. Empty parts and repeats are dropped. The order is kept. -
i2pd. Each
i2pd.confof R2, in that order. The keys of the[httpproxy]section count. A keyhttpproxy.<key>before the first section counts as<key>of[httpproxy]too (i2pd reads both forms).#starts a comment. A file with neither has no HTTP proxy; an empty section uses the defaults.enabledis off when it isfalse,0,nooroff, in any case. The section is the HTTP proxy whenenabledis not off andport(default4444) is the proxy port. Its outproxies are the value ofoutproxy, split on,, each part trimmed, empty parts dropped. -
Which files. Router type Java I2P: the Java I2P files only. i2pd: the i2pd files only. Type not known: both. Within one Java I2P folder, or one
i2pd.conf, the first HTTP proxy found wins. eepview reads every folder and every file of the type. When more than one has an HTTP proxy on the port and their outproxy lists differ:not-checked, and the detail says that the router configurations disagree and names the files. A stale~/.i2pfrom an old install can sit next to the configuration of the router that runs, and eepview cannot tell which one is in use. When they agree, the first file in the order of R2 is the one named. When the type is not known and both types have an HTTP proxy on the port:not-checked, and the detail says that both a Java I2P and an i2pd configuration use that port. -
Result. No HTTP proxy found on the port:
not-checked. The detail says that no router configuration with an HTTP proxy on that port was found. An HTTP proxy with no outproxy:passed. The detail names the file. An HTTP proxy with outproxies:failed. The detail names each outproxy. - eepview only reads these files. A missing or unreadable file counts as no HTTP proxy. It reads them again only when the list of files or the modification time of one of them changed since the last round. A detail names a file by its name only, never by its folder.
- A router that eepview manages (Phase 3) gets a configuration from eepview with no outproxy. This same rule reads it.
- The proxy port is the port of
-
V13 Check 4, network and tunnels.
- No statistics (V10):
not-checked. The detail says that the router statistics are not available. -
networkStatusset and notOK:failed. The detail names the status, for exampleFIREWALLED. -
tunnels.clientis0:failed. The detail says that no client tunnel is built yet. -
networkStatusisOKandtunnels.clientis 1 or more:passed. The detail names the client tunnel count. - Else, one of the two figures is
null:not-checked. The detail names the figure that the router does not report. i2pd never reportstunnels.client(R38). - A failure wins over a missing figure:
networkStatusTESTINGwithtunnels.clientnullisfailed.
- No statistics (V10):
-
V14 Events.
router-statuscarrieschecks. eepview emits it on every VERIFY round, as before, and also when check 1 turnsrunning(V5) and when checks 2 to 4 change theirstate,detailorpassedAt. A round that changes nothing in checks 2 to 4 emits nothing for them.
-
V15 Four rows. The card shows the four checks as an ordered list (
ol#router-checks, label "Router checks"), in the order of V1. Each row (li.check,data-check="<id>",data-state="<state>") shows the name of the check (.check-name), its state as text (.check-state), and its detail (.check-detail) when it has one. -
V16 State text. The state is always written as text, not only as a color:
stateText pendingWaiting runningChecking passedPassed at HH:MM:SS(24-hour local time ofpassedAt, two digits each)failedFailed not-checkedNot checked A
passedcheck withpassedAtnullshows "Passed". -
V17 Only real state. A row looks passed (the accent mark) only while its check is
passed. The page changes a row only when arouter-statusevent or therouter_status()answer brings newchecks. No timer, no CSS animation and no CSS transition changes a row. ARouterStatuswith nochecksshows four rows "Waiting". -
V18 Announcements. A polite live region (
#router-checks-live,role="status") says ": ." for each check whosestatechanged, in check order, one sentence each. It says nothing on the first render, and nothing when only adetailor apassedAtchanged. -
V19 The rest of the card. The state chip and line, Router, Proxy, "Network details" and the "Router console" section (Router console, R15) do not change.
-
V20 Look. The rows use the shared component
.checks(UI components): palette tokens only, a mark per state that isaria-hidden, and the state text next to it.faileduses the danger color,passedthe accent color,not-checkedandpendingthe muted color.
type CheckId = "proxy-i2p" | "version" | "no-outproxy" | "tunnels";
type CheckState = "pending" | "running" | "passed" | "failed" | "not-checked";
type VerifyCheck = { id: CheckId; state: CheckState; detail: string | null;
passedAt: number | null }; // Unix ms; null unless state is "passed"
type RouterStatus = { /* v1.8 fields */ checks: VerifyCheck[] }; // V1: always 4, in order#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")] // "proxy-i2p" | "version" | "no-outproxy" | "tunnels"
pub enum CheckId { ProxyI2p, Version, NoOutproxy, Tunnels }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")] // "pending" | "running" | "passed" | "failed" | "not-checked"
pub enum CheckState { Pending, Running, Passed, Failed, NotChecked }
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifyCheck { pub id: CheckId, pub state: CheckState,
pub detail: Option<String>, pub passed_at: Option<u64> }
/// The `router_status()` answer and the `router-status` payload: every `RouterStatus`
/// field, then `checks`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct RouterReport { #[serde(flatten)] pub status: RouterStatus, pub checks: Vec<VerifyCheck> }RouterStatus keeps its fields. The checks live next to it in the core.
pub const JAVA_MIN_VERSION: (u64, u64, u64) = (2, 4, 0);
pub const I2PD_MIN_VERSION: (u64, u64, u64) = (2, 50, 0);
/// One run of a check.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome { Passed(Option<String>), Failed(String), NotChecked(String) }
/// V11: `1.2.3`, `1.2`, `1`, each with an optional `-<text>` suffix. `None` for other text.
pub fn parse_version(text: &str) -> Option<(u64, u64, u64)>;
pub fn version_outcome(kind: Option<RouterKind>, version: Option<&str>) -> Outcome; // V11
pub fn outproxy_outcome(finding: &OutproxyFinding) -> Outcome; // V12
pub fn tunnels_outcome(stats: Option<&RouterStats>) -> Outcome; // V13
/// The facts of one round (V10).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CheckFacts { pub kind: Option<RouterKind>, pub version: Option<String>,
pub outproxy: OutproxyFinding, pub stats: Option<RouterStats> }RouterKind is eepview_lib::net::outproxy::RouterKind, a re-export of eepview_lib::net::console::ConsoleKind (the same type). The core names it RouterKind, because the core never names net::console (R29 of Router console). RouterStats is eepview_lib::net::stats::RouterStats (Default gives every field None).
impl Core {
pub fn checks(&self) -> &[VerifyCheck]; // V1
pub fn router_report(&self) -> RouterReport; // V1, V14
/// V5: a VERIFY round starts. Emits `Event::Router` only when check 1 changed.
pub fn verify_started(&mut self) -> Vec<Effect>;
/// The result of a VERIFY round at `now` (Unix ms): `router_changed(status)`, then V6, V7, V9.
pub fn router_checked(&mut self, now: u64, status: RouterStatus) -> Vec<Effect>;
/// V9 to V14: checks 2 to 4 from the facts of a round at `now`. Does nothing while the
/// gate is closed or the connection is paused. Emits `Event::Router` only on a change.
pub fn checks_seen(&mut self, now: u64, facts: &CheckFacts) -> Vec<Effect>;
}Core::new, router_changed, pause and resume keep their signatures. router_changed(status) alone does not change the checks. pause() and resume() do (V8).
/// What the router configuration says about the HTTP proxy on one port (V12).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OutproxyFinding {
/// The HTTP proxy is in `file` (file name only) and lists no outproxy.
Clear { file: String },
/// The HTTP proxy is in `file` and lists these outproxies.
Listed { file: String, outproxies: Vec<String> },
/// eepview could not tell: the reason.
Unknown(String),
}
/// The outproxies of the `httpclient` tunnel on `port` in one Java I2P tunnel file
/// (`i2ptunnel.config` or one file of `i2ptunnel.config.d/`). `None`: no such tunnel.
pub fn java_outproxies(config: &str, port: u16) -> Option<Vec<String>>;
/// The outproxies of the `[httpproxy]` section of one `i2pd.conf` when it serves `port`.
pub fn i2pd_outproxies(i2pd_conf: &str, port: u16) -> Option<Vec<String>>;
/// The files `find_outproxy` reads for `kind`, each with its modification time (`None`
/// when it cannot be read). Equal stamps mean an equal finding.
pub fn config_stamps(kind: Option<RouterKind>, env: &dyn Fn(&str) -> Option<String>)
-> Vec<(PathBuf, Option<SystemTime>)>;
/// V12 over the files of this OS (the folders of `net::console::java_config_dirs` and the
/// files of `net::console::i2pd_config_files`, with the same `env`).
pub use crate::net::console::ConsoleKind as RouterKind;
pub fn find_outproxy(kind: Option<RouterKind>, port: u16,
env: &dyn Fn(&str) -> Option<String>) -> OutproxyFinding;export const CHECK_NAMES: Record<CheckId, string>; // the names of the table above
export interface CheckView { id: CheckId; name: string; state: CheckState; stateText: string;
detail: string | null }
/** V15–V17: the four rows, in order. A missing or short list gives "Waiting" rows. */
export function checkViews(checks: VerifyCheck[] | null | undefined): CheckView[];
/** V16: `HH:MM:SS`, 24-hour local time. */
export function clockTime(ms: number): string;
/** V18: the announcement for a change, "" when there is nothing to say. */
export function checkAnnouncement(before: VerifyCheck[] | null | undefined,
after: VerifyCheck[] | null | undefined): string;CheckId, CheckState and VerifyCheck are in src/ui/contract.ts. The dev mock (src/ui/mock.ts) answers router_status() with four checks that follow its mock router state, and ?checks=mixed previews a failed and a not-checked check.
-
src-tauri/tests/router_checks.rs(V1–V14, the core and the outcomes) -
src-tauri/tests/router_checks_outproxy.rs(V12, the configuration files) -
src/ui/lib/router-checks.test.ts(V15–V18) -
src/ui/home-checks.test.ts(V15–V19, the Home page on the stand-in shell) -
src/ui/mock-contract.test.ts(V1, the dev mock)
- Checks 2 to 4 need the router console for the router type and, without a router helper, for the statistics. Detection runs when the Home page loads as the active tab (Router console, R6). Until then, checks 2 and 4 are
not-checked. - Java I2P ships with an outproxy in its HTTP proxy tunnel. So on a default Java I2P router, check 3 fails. eepview still never uses it (ADR 0001, layer L1).
- eepview cannot see an outproxy set on the i2pd command line.
- 2026-10-06 — The router card shows the four real checks instead of the decorative hop path — #84.
Generated from docs/wiki in the repository. Edit there, not here.