diff --git a/docs/build-status.md b/docs/build-status.md index 3e43a0b..32eed11 100644 --- a/docs/build-status.md +++ b/docs/build-status.md @@ -478,3 +478,20 @@ app**: `applicationIdSuffix .dev`, label "Echolot DEV", and a DEV-badged icon (l production foreground + generated amber DEV ribbon) so it is unmistakable next to a real install and both can be installed side by side. NOTE for tooling: the dev package is `app.echo_lot.app.dev`, activity `app.echo_lot.app.MainActivity`. + +### link.ra_source verified on-device — it named the actual router (2026-08-01) +Run archived at `echolot-app/reports/CPH2747-app-run5-router-id.json`. On the wifi network the +probe identified the RA sender completely, from an unprivileged app: +- RA source `fe80::7a9a:18ff:fe54:b8f9` → **MAC 78:9A:18:54:B8:F9 recovered via EUI-64** + (matches the Shizuku neighbor table exactly) → vendor **MikroTik** by OUI +- IPv4 gateway `10.13.102.1`, reverse DNS `router.hudelist.local` +- UPnP: `RouterOS/7.23.2 UPnP/1.0 MikroTik` → manufacturer MikroTik, model Router OS, + friendly name "MikroTik Router" +So the box advertising this LAN's broken IPv6 RA is a **MikroTik running RouterOS 7.23.2**, named +by two independent methods (OUI from the address itself + UPnP device description) that corroborate. +On cellular the carrier's RA source is an RFC 7217 privacy address and is correctly reported as +"not EUI-64" rather than guessed. +The SSDP sweep also inventoried the LAN (Synology DS1522+ DSM 7.3, a Sky ES160 gateway) — the +raw material for the planned LLDP/mDNS cross-matching by MAC. +Fixes from this run: added the confirmed MikroTik OUI 78:9A:18 (+ other RouterBOARD ranges), and +an elvis-operator bug that printed "no UPnP response" even when UPnP data was present. diff --git a/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/Oui.kt b/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/Oui.kt index 5d52b12..1b087f3 100644 --- a/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/Oui.kt +++ b/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/Oui.kt @@ -25,6 +25,8 @@ object Oui { // MikroTik "00:0C:42" to "MikroTik", "4C:5E:0C" to "MikroTik", "6C:3B:6B" to "MikroTik", "48:8F:5A" to "MikroTik", "2C:C8:1B" to "MikroTik", "DC:2C:6E" to "MikroTik", + "78:9A:18" to "MikroTik", "18:FD:74" to "MikroTik", "64:D1:54" to "MikroTik", + "74:4D:28" to "MikroTik", "C4:AD:34" to "MikroTik", "E4:8D:8C" to "MikroTik", // TP-Link "00:1D:0F" to "TP-Link", "14:CC:20" to "TP-Link", "50:C7:BF" to "TP-Link", "A4:2B:B0" to "TP-Link", "C0:06:C3" to "TP-Link", "EC:08:6B" to "TP-Link", diff --git a/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/RouterIdentityProbe.kt b/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/RouterIdentityProbe.kt index 0423281..6b718fc 100644 --- a/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/RouterIdentityProbe.kt +++ b/echolot-app/core-probe/src/main/kotlin/app/echo_lot/probe/RouterIdentityProbe.kt @@ -80,15 +80,18 @@ class RouterIdentityProbe(private val entries: List) : P put("v4_gateway", v4Gw ?: "(none)") if (v4Gw != null) { put("v4_gateway_reverse_dns", reverseDns(v4Gw)) - ssdp[v4Gw]?.let { s -> - put("upnp_server", s.server) - put("upnp_location", s.location) - s.details?.let { d -> + val gwSsdp = ssdp[v4Gw] + if (gwSsdp != null) { + put("upnp_server", gwSsdp.server) + put("upnp_location", gwSsdp.location) + gwSsdp.details?.let { d -> put("upnp_manufacturer", d.manufacturer) put("upnp_model", d.model) put("upnp_friendly_name", d.friendlyName) } - } ?: put("upnp", "no UPnP/SSDP response from the gateway") + } else { + put("upnp", "no UPnP/SSDP response from the gateway") + } } } } diff --git a/echolot-app/reports/CPH2747-app-run5-router-id.json b/echolot-app/reports/CPH2747-app-run5-router-id.json new file mode 100644 index 0000000..58ec9ca --- /dev/null +++ b/echolot-app/reports/CPH2747-app-run5-router-id.json @@ -0,0 +1,489 @@ +{ + "schema": "echolot/measurement", + "schema_version": "1.0.0", + "run": { + "id": "19c4f4ea-9904-43af-9e29-60750dea99a6", + "trigger": "manual", + "started_at": "2026-08-01T07:40:42.502108Z", + "ended_at": "2026-08-01T07:40:57.944115Z", + "clock": { + "mono_origin_wall": "2026-08-01T07:40:42.502108Z", + "ntp_offset_ms": null, + "ntp_offset_source": null + }, + "app": { + "version": "0.1.0-dev", + "build": 1, + "git": null, + "flavor": "app" + }, + "device": { + "manufacturer": "OnePlus", + "model": "CPH2747", + "android_sdk": 36, + "android_release": "16", + "security_patch": null + }, + "tiers": { + "app": true, + "shizuku": false, + "root": false + }, + "profiles_used": [], + "notes": null + }, + "networks": [ + { + "id": "net-0", + "transport": "cellular", + "interface": "rmnet_data4", + "link": { + "mtu": 1500, + "addresses": [ + { + "addr": "2001:4bb8:417:bd78:e4fe:8cff:febe:ca8c", + "prefix_len": 64, + "scope": null, + "flags": [], + "valid_lft_s": null, + "pref_lft_s": null + } + ], + "routes": [ + { + "dst": "::/0", + "gateway": "fe80::246f:12be:21ef:1b54", + "iface": "rmnet_data4", + "proto": null, + "expires_s": null + }, + { + "dst": "2001:4bb8:417:bd78::/64", + "gateway": "::", + "iface": "rmnet_data4", + "proto": null, + "expires_s": null + } + ], + "dns": { + "servers": [ + "fda1:3fb1:0:8:0:10:0:101", + "fda1:3fb1:0:8:0:10:0:100" + ], + "private_dns_mode": "off", + "private_dns_hostname": null, + "search_domains": [], + "nat64_prefix": null + }, + "dhcp": null, + "captive_portal": null + }, + "wifi": null, + "cellular": null, + "changes": [] + }, + { + "id": "net-1", + "transport": "wifi", + "interface": "wlan0", + "link": { + "mtu": null, + "addresses": [ + { + "addr": "fe80::7a:75ff:fee9:ae9e", + "prefix_len": 64, + "scope": null, + "flags": [], + "valid_lft_s": null, + "pref_lft_s": null + }, + { + "addr": "10.13.102.124", + "prefix_len": 24, + "scope": null, + "flags": [], + "valid_lft_s": null, + "pref_lft_s": null + } + ], + "routes": [ + { + "dst": "fe80::/64", + "gateway": "::", + "iface": "wlan0", + "proto": null, + "expires_s": null + }, + { + "dst": "::/0", + "gateway": "fe80::7a9a:18ff:fe54:b8f9", + "iface": "wlan0", + "proto": null, + "expires_s": null + }, + { + "dst": "10.13.102.0/24", + "gateway": "0.0.0.0", + "iface": "wlan0", + "proto": null, + "expires_s": null + }, + { + "dst": "0.0.0.0/0", + "gateway": "10.13.102.1", + "iface": "wlan0", + "proto": null, + "expires_s": null + } + ], + "dns": { + "servers": [ + "10.13.102.1" + ], + "private_dns_mode": "off", + "private_dns_hostname": null, + "search_domains": [ + "hudelist.local" + ], + "nat64_prefix": null + }, + "dhcp": null, + "captive_portal": null + }, + "wifi": null, + "cellular": null, + "changes": [] + } + ], + "server_sessions": [], + "tests": [ + { + "id": "742cdc50-65ef-477b-82de-0f44415a64a5", + "type": "link.snapshot", + "network_ref": null, + "session_ref": null, + "tier": "app", + "started_mono_ns": 7575833, + "ended_mono_ns": 10050104, + "status": "ok", + "error": null, + "params": null, + "evidence": { + "network_count": 2, + "networks": [ + { + "id": "net-0", + "transport": "cellular", + "interface": "rmnet_data4", + "mtu": 1500, + "addresses": "2001:4bb8:417:bd78:e4fe:8cff:febe:ca8c/64", + "dns": "fda1:3fb1:0:8:0:10:0:101, fda1:3fb1:0:8:0:10:0:100", + "nat64": "none" + }, + { + "id": "net-1", + "transport": "wifi", + "interface": "wlan0", + "mtu": 0, + "addresses": "fe80::7a:75ff:fee9:ae9e/64, 10.13.102.124/24", + "dns": "10.13.102.1", + "nat64": "none" + } + ] + }, + "metrics": null + }, + { + "id": "e474eb97-4ccf-4a7d-bd4d-f84f11038a35", + "type": "link.ra_source", + "network_ref": null, + "session_ref": null, + "tier": "app", + "started_mono_ns": 10469688, + "ended_mono_ns": 4988146769, + "status": "ok", + "error": null, + "params": null, + "evidence": { + "networks": [ + { + "network": "cellular:net-0", + "interface": "rmnet_data4", + "ra_source": "fe80::246f:12be:21ef:1b54", + "ra_source_mac": "(not EUI-64 — privacy/RFC 7217 address)", + "ra_source_reverse_dns": "(none)", + "v4_gateway": "(none)" + }, + { + "network": "wifi:net-1", + "interface": "wlan0", + "ra_source": "fe80::7a9a:18ff:fe54:b8f9", + "ra_source_mac": "78:9A:18:54:B8:F9", + "ra_source_vendor": "MikroTik", + "ra_source_mac_locally_administered": false, + "ra_source_reverse_dns": "(none)", + "v4_gateway": "10.13.102.1", + "v4_gateway_reverse_dns": "router.hudelist.local", + "upnp_server": "RouterOS/7.23.2UPnP/1.0 MikroTik UPnP/1.0", + "upnp_location": "http://10.13.102.1:2828/gateway.xml", + "upnp_manufacturer": "MikroTik", + "upnp_model": "Router OS", + "upnp_friendly_name": "MikroTik Router" + } + ], + "ssdp_responders": [ + { + "ip": "10.13.102.1", + "server": "RouterOS/7.23.2UPnP/1.0 MikroTik UPnP/1.0", + "location": "http://10.13.102.1:2828/gateway.xml", + "manufacturer": "MikroTik", + "model": "Router OS", + "friendly_name": "MikroTik Router" + }, + { + "ip": "10.13.102.5", + "server": "Synology/DSM/10.13.102.5", + "location": "http://10.13.102.5:5000/ssdp/desc-DSM-ovs_eth0.xml", + "manufacturer": "Synology", + "model": "DS1522+ DS1522+ 7.3-86009", + "friendly_name": "hude-nas (DS1522+)" + }, + { + "ip": "10.13.102.116", + "server": "GW-Device/1.0.0 (Sky, ES160, )", + "location": "http://10.13.102.116:49153/description2.xml" + } + ] + }, + "metrics": { + "ra_senders": 2, + "ssdp_responders": 3 + } + }, + { + "id": "194cd65d-b592-466d-b741-c119e2e11e2c", + "type": "icmp.ping4", + "network_ref": null, + "session_ref": null, + "tier": "app", + "started_mono_ns": 4989069529, + "ended_mono_ns": 5067067706, + "status": "ok", + "error": null, + "params": null, + "evidence": { + "target": "1.1.1.1", + "default": "reply type=0 rtt_ms=37.1 bytes=15", + "cellular:net-0": "error: Binding socket to network 137 failed: EPERM (Operation not permitted)", + "wifi:net-1": "reply type=0 rtt_ms=37.5 bytes=15" + }, + "metrics": { + "networks_ok": 2, + "rtt_ms_min": 37.1, + "rtt_ms_avg": 37.3, + "rtt_ms_max": 37.5 + } + }, + { + "id": "935f6029-b424-41e3-b706-57de28b16738", + "type": "icmp.ping6", + "network_ref": null, + "session_ref": null, + "tier": "app", + "started_mono_ns": 5067683748, + "ended_mono_ns": 11360936454, + "status": "failed", + "error": null, + "params": null, + "evidence": { + "target": "2606:4700:4700::1111", + "default": "error: recvfrom failed: EAGAIN (Try again)", + "cellular:net-0": "error: Binding socket to network 137 failed: EPERM (Operation not permitted)", + "wifi:net-1": "error: recvfrom failed: EAGAIN (Try again)" + }, + "metrics": { + "networks_ok": 0 + } + }, + { + "id": "8981bc28-f499-4eb9-956d-755854fc5ace", + "type": "net.captive_portal", + "network_ref": null, + "session_ref": null, + "tier": "app", + "started_mono_ns": 11361780100, + "ended_mono_ns": 12224197756, + "status": "ok", + "error": null, + "params": null, + "evidence": { + "https_url": "https://www.google.com/generate_204", + "http_url": "http://connectivitycheck.gstatic.com/generate_204", + "default": { + "https_code": 204, + "http_code": 204, + "verdict": "validated" + }, + "cellular:net-0": { + "https_code": -1, + "http_code": -1, + "verdict": "no_internet" + }, + "wifi:net-1": { + "https_code": 204, + "http_code": 204, + "verdict": "validated" + } + }, + "metrics": null + }, + { + "id": "84894107-55db-40cd-9aec-0a7b95dc1c19", + "type": "dns.canary", + "network_ref": null, + "session_ref": null, + "tier": "app", + "started_mono_ns": 12225064683, + "ended_mono_ns": 12342101870, + "status": "ok", + "error": null, + "params": null, + "evidence": { + "zone": "c.echo-lot.app", + "reference_records": { + "ttl-5": { + "fqdn": "ttl-5.c.echo-lot.app", + "expected": "192.0.2.5", + "got": "192.0.2.5", + "verdict": "match" + }, + "ttl-60": { + "fqdn": "ttl-60.c.echo-lot.app", + "expected": "192.0.2.60", + "got": "192.0.2.60", + "verdict": "match" + }, + "ttl-3600": { + "fqdn": "ttl-3600.c.echo-lot.app", + "expected": "192.0.2.36", + "got": "192.0.2.36", + "verdict": "match" + }, + "ttl-86400": { + "fqdn": "ttl-86400.c.echo-lot.app", + "expected": "192.0.2.86", + "got": "192.0.2.86", + "verdict": "match" + } + }, + "nonce_query": { + "fqdn": "b5377d93.adhoc.c.echo-lot.app", + "got": "192.0.2.222", + "reached_authoritative": true, + "note": "a non-192.0.2.x answer means something other than the canary server replied" + } + }, + "metrics": { + "references_matched": 4, + "references_mismatched": 0, + "references_failed": 0 + } + }, + { + "id": "7d6d3a54-bce5-4ee0-855d-dafdd32310b7", + "type": "nat.stun_5780", + "network_ref": null, + "session_ref": null, + "tier": "app", + "started_mono_ns": 12342829787, + "ended_mono_ns": 15433441505, + "status": "ok", + "error": null, + "params": null, + "evidence": { + "server": "fmr-1.echo-lot.app:3478", + "local_addr": "10.13.102.124", + "local_port": 58152, + "mapped": "178.191.120.247:58152", + "other_address": "89.185.109.151:3479", + "mapped_via_alt": "178.191.120.247:58152", + "change_port_reply": "none", + "mapping_behavior": "endpoint-independent (one mapping for all destinations — P2P friendly)", + "filtering_behavior": "address/port-dependent (drops inbound from endpoints not contacted)", + "behind_nat": true + }, + "metrics": { + "mapped_port": 58152, + "local_addr": "10.13.102.124", + "local_port": 58152, + "port_preserved": true, + "behind_nat": true + } + }, + { + "id": "28280b92-8086-4264-9161-76e3576d21da", + "type": "link.ip_monitor", + "network_ref": null, + "session_ref": null, + "tier": "shizuku", + "started_mono_ns": 15434620359, + "ended_mono_ns": 15437705567, + "status": "unsupported", + "error": null, + "params": null, + "evidence": { + "binder_alive": false, + "detail": "Shizuku not running" + }, + "metrics": null + } + ], + "findings": [ + { + "id": "3efd13e4-6517-42b1-a8c1-37ed60971878", + "code": "ipv6.broken", + "category": "ipv6", + "severity": "medium", + "confidence": "high", + "network_ref": null, + "title": "IPv6 is configured but not working", + "description": "This network advertises IPv6 (a global address and/or a default route), but ICMPv6 got no reply on any network. Half-configured IPv6 is worse than none: connections try IPv6 first and stall before falling back.", + "evidence_refs": [ + { + "test": "935f6029-b424-41e3-b706-57de28b16738", + "pointer": null + } + ], + "recommendation": null + } + ], + "summary": { + "overall": "yellow", + "categories": { + "connectivity": { + "verdict": "green", + "worst_finding": null, + "tests_run": 6, + "tests_failed": 2 + }, + "dns": { + "verdict": "green", + "worst_finding": null, + "tests_run": 1, + "tests_failed": 0 + }, + "nat": { + "verdict": "green", + "worst_finding": null, + "tests_run": 1, + "tests_failed": 0 + }, + "ipv6": { + "verdict": "yellow", + "worst_finding": "3efd13e4-6517-42b1-a8c1-37ed60971878", + "tests_run": 0, + "tests_failed": 0 + } + } + } +} \ No newline at end of file