vpn toggle: status, not a bool
It read "On" for an hour through a dead tunnel — NM said activated while wg had 368 B received against 5.3 KiB sent. Now off/connecting/limited/online, decided by a 2s probe of the far-side resolver rather than by the interface being up. Sailfish models the same distinction (Online/Limited/Connecting); Android won't let an unvalidated network win at all.
This commit is contained in:
parent
189de61cbe
commit
0745b48dc5
1 changed files with 71 additions and 8 deletions
|
|
@ -39,12 +39,54 @@ QuickToggleModel {
|
|||
/// True while an up/down is in flight, so a double tap cannot race itself.
|
||||
property bool busy: false
|
||||
|
||||
/// A host that only exists at the far end of the tunnel. Reaching it is the
|
||||
/// only honest proof the tunnel carries traffic.
|
||||
property string probeHost: "10.10.20.5"
|
||||
|
||||
/// "off" | "connecting" | "limited" | "online".
|
||||
///
|
||||
/// NOT a bool, and that is the point. On 2026-07-31 this toggle read "On"
|
||||
/// for an hour while the tunnel was deaf: NetworkManager said `activated`,
|
||||
/// `wg` said 368 B received against 5.3 KiB sent, and every service behind
|
||||
/// it — STT, TTS, gitea — was unreachable. "The interface is up" and "the
|
||||
/// tunnel carries traffic" are different claims and only one of them is
|
||||
/// worth showing.
|
||||
///
|
||||
/// Sailfish models exactly this distinction (`MobileDataConnection.status`
|
||||
/// is Online / Limited / Connecting, never a bool) and Android refuses to
|
||||
/// let an unvalidated network win at all. `limited` is the state both have
|
||||
/// and we did not.
|
||||
///
|
||||
/// The probe is deliberately NOT an internet check. This is a split tunnel
|
||||
/// whose whole purpose is a private 10.10.0.0/16 that no public probe can
|
||||
/// see; a link that fails a general-internet probe may be exactly the one
|
||||
/// we want. The question is per-consumer — "can this carry *this*" — so we
|
||||
/// ask the far end directly.
|
||||
property string status: "off"
|
||||
|
||||
name: Translation.tr("Wiuf VPN")
|
||||
icon: root.toggled ? "vpn_lock" : "vpn_key"
|
||||
statusText: root.busy ? Translation.tr("…") : (root.toggled ? Translation.tr("On") : Translation.tr("Off"))
|
||||
tooltipText: root.toggled
|
||||
? Translation.tr("Wiuf VPN — 10.10.0.0/16 routed over the tunnel")
|
||||
: Translation.tr("Wiuf VPN — off")
|
||||
icon: root.status === "online" ? "vpn_lock"
|
||||
: root.status === "limited" ? "vpn_lock_off"
|
||||
: "vpn_key"
|
||||
statusText: {
|
||||
if (root.busy)
|
||||
return Translation.tr("…");
|
||||
switch (root.status) {
|
||||
case "online": return Translation.tr("On");
|
||||
case "limited": return Translation.tr("No route");
|
||||
default: return Translation.tr("Off");
|
||||
}
|
||||
}
|
||||
tooltipText: {
|
||||
switch (root.status) {
|
||||
case "online":
|
||||
return Translation.tr("Wiuf VPN — 10.10.0.0/16 routed over the tunnel");
|
||||
case "limited":
|
||||
return Translation.tr("Wiuf VPN — connected but not carrying traffic. The tunnel is up and the far side is unreachable; check which link the handshake is leaving by.");
|
||||
default:
|
||||
return Translation.tr("Wiuf VPN — off");
|
||||
}
|
||||
}
|
||||
|
||||
mainAction: () => {
|
||||
if (root.busy)
|
||||
|
|
@ -86,16 +128,37 @@ QuickToggleModel {
|
|||
// brought up or down from nmcli, from Settings, or by NM itself, and a
|
||||
// shadow copy that disagrees with the owning subsystem is the failure
|
||||
// doctrine §4 names ("never a hand-tracked bool").
|
||||
// Two questions, asked together because the answer to the first does not
|
||||
// imply the second: is the connection active, and does it carry traffic.
|
||||
// A single `nmcli` reply can only answer the first, which is how this
|
||||
// reported "On" through an hour of a dead tunnel.
|
||||
//
|
||||
// The probe is one UDP DNS round-trip to the far-side resolver with a 2 s
|
||||
// deadline — cheap enough for a 15 s poll, and it fails in exactly the case
|
||||
// that matters (interface up, nothing crossing it). `getent` is not used:
|
||||
// it would consult the whole resolver list and could be answered by a
|
||||
// nameserver on another link, which is the same conflation this is here to
|
||||
// end.
|
||||
Process {
|
||||
id: refreshProc
|
||||
running: true
|
||||
command: ["bash", "-c", `nmcli -t -f NAME connection show --active | grep -qx '${root.connectionName}' && echo up || echo down`]
|
||||
command: ["bash", "-c",
|
||||
`if ! nmcli -t -f NAME connection show --active | grep -qx '${root.connectionName}'; then echo off; exit 0; fi;`
|
||||
+ ` if timeout 2 bash -c 'echo > /dev/udp/${root.probeHost}/53' 2>/dev/null`
|
||||
+ ` && timeout 2 ping -c1 -W2 ${root.probeHost} >/dev/null 2>&1; then echo online; else echo limited; fi`]
|
||||
stdout: StdioCollector {
|
||||
id: stateCollector
|
||||
onStreamFinished: {
|
||||
if (stateCollector.text.length === 0)
|
||||
const reply = stateCollector.text.trim();
|
||||
if (reply.length === 0)
|
||||
return;
|
||||
root.toggled = stateCollector.text.trim() === "up";
|
||||
root.status = reply;
|
||||
// `toggled` stays the user-facing "is it switched on", so the
|
||||
// button still lights while the tunnel is limited — the state
|
||||
// is reported in statusText rather than by silently un-toggling
|
||||
// something the user turned on. Sailfish does the same: the
|
||||
// switch stays checked and the description says "Limited".
|
||||
root.toggled = (reply !== "off");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue