Compare commits

..

2 commits

Author SHA1 Message Date
59861872f4 network: track connecting/failed state from Connect() reply
Network.Connected can flicker true mid-handshake, so a row could show ✔
even while the passphrase was still being verified (and stay ✔ on a bad
PSK). Add a local per-network conn_status (IDLE / CONNECTING / FAILED)
driven by iwd's Connect() reply, which only returns once the attempt
truly concludes.

- iwd_network_connect() sets CONNECTING and refreshes immediately.
- The connect reply ctx now carries the object path, re-resolved through
  the live hash (same UAF-safe pattern as the SSID); on error → FAILED,
  on success → IDLE.
- A confirmed Connected=true clears the marker back to IDLE.
- popup row marker priority: … (connecting) and ! (failed) take
  precedence over ✔, with matching accessibility text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 14:55:20 +09:00
0f34fe39e0 manager: clear known_path when KnownNetwork object is removed
Forgetting a saved network removes the KnownNetwork D-Bus object, but the
matching Network.KnownNetwork change arrives only as a property
invalidation, which our changed-only handler drops. As a result
n->known_path stayed stale and the ★ marker and Forget (✕) button kept
showing until the next full repopulate (radio off/on).

Add an IWD_IFACE_KNOWN_NETWORK branch to _on_iface_removed that clears
known_path on any network pointing at the removed object, then notifies,
so the markers vanish immediately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 14:55:11 +09:00
4 changed files with 82 additions and 10 deletions

View file

@ -257,13 +257,24 @@ _rebuild_list(Popup *p)
memcpy(ssid_buf + copy, "\xe2\x80\xa6", 4); /* "…" + NUL */ memcpy(ssid_buf + copy, "\xe2\x80\xa6", 4); /* "…" + NUL */
raw_ssid = ssid_buf; raw_ssid = ssid_buf;
} }
/* Connection marker: connecting (…) and failure (!) take priority
* over Connected, which iwd may briefly assert mid-handshake. */
const char *mark, *amark;
switch (n->conn_status)
{
case IWD_CONN_CONNECTING: mark = ""; amark = ", connecting"; break;
case IWD_CONN_FAILED: mark = " !"; amark = ", failed"; break;
default:
mark = n->connected ? "" : "";
amark = n->connected ? ", connected" : "";
}
char label[256]; char label[256];
snprintf(label, sizeof(label), "%s %s%s [%s]%s", snprintf(label, sizeof(label), "%s %s%s [%s]%s",
_signal_bars(iwd_network_signal_tier(n)), _signal_bars(iwd_network_signal_tier(n)),
n->known_path ? "" : " ", n->known_path ? "" : " ",
raw_ssid, raw_ssid,
iwd_security_label(n->security), iwd_security_label(n->security),
n->connected ? "" : ""); mark);
elm_object_text_set(btn, label); elm_object_text_set(btn, label);
/* Spoken label: avoids the ▂▄▆█ glyphs and ★/✔ markers which /* Spoken label: avoids the ▂▄▆█ glyphs and ★/✔ markers which
* screen readers announce as raw Unicode. */ * screen readers announce as raw Unicode. */
@ -274,7 +285,7 @@ _rebuild_list(Popup *p)
iwd_network_signal_tier(n), iwd_network_signal_tier(n),
iwd_security_label(n->security), iwd_security_label(n->security),
n->known_path ? ", saved" : "", n->known_path ? ", saved" : "",
n->connected ? ", connected" : ""); amark);
elm_object_access_info_set(btn, access); elm_object_access_info_set(btn, access);
evas_object_size_hint_weight_set(btn, EVAS_HINT_EXPAND, 0); evas_object_size_hint_weight_set(btn, EVAS_HINT_EXPAND, 0);
evas_object_size_hint_align_set(btn, EVAS_HINT_FILL, 0); evas_object_size_hint_align_set(btn, EVAS_HINT_FILL, 0);

View file

@ -245,6 +245,20 @@ _on_iface_removed(void *data, const char *path, const char *iface)
{ {
eina_hash_del(m->networks, path, NULL); eina_hash_del(m->networks, path, NULL);
} }
else if (!strcmp(iface, IWD_IFACE_KNOWN_NETWORK))
{
/* A saved network was forgotten: iwd removes the KnownNetwork object
* but the matching Network.KnownNetwork change arrives only as an
* invalidation, which our changed-only property handler drops. Clear
* the back-reference here so the marker and Forget () button vanish
* immediately instead of lingering until the next full repopulate. */
Eina_Iterator *it = eina_hash_iterator_data_new(m->networks);
Iwd_Network *n;
EINA_ITERATOR_FOREACH(it, n)
if (n->known_path && !strcmp(n->known_path, path))
{ free(n->known_path); n->known_path = NULL; }
eina_iterator_free(it);
}
else if (!strcmp(iface, IWD_IFACE_ADAPTER)) else if (!strcmp(iface, IWD_IFACE_ADAPTER))
{ {
eina_hash_del(m->adapters, path, NULL); eina_hash_del(m->adapters, path, NULL);

View file

@ -22,7 +22,12 @@ _prop_cb(void *data, const char *key, Eldbus_Message_Iter *v)
Iwd_Network *n = data; Iwd_Network *n = data;
if (!strcmp(key, "Name")) { free(n->ssid); n->ssid = iwd_props_str_dup(v); } if (!strcmp(key, "Name")) { free(n->ssid); n->ssid = iwd_props_str_dup(v); }
else if (!strcmp(key, "Type")) { char *s = iwd_props_str_dup(v); n->security = _sec_from_str(s); free(s); } else if (!strcmp(key, "Type")) { char *s = iwd_props_str_dup(v); n->security = _sec_from_str(s); free(s); }
else if (!strcmp(key, "Connected")) { n->connected = iwd_props_bool(v); } else if (!strcmp(key, "Connected")) {
n->connected = iwd_props_bool(v);
/* A confirmed connection clears any in-flight/failed marker so the
* row settles on (and a stale ! from a prior attempt disappears). */
if (n->connected) n->conn_status = IWD_CONN_IDLE;
}
else if (!strcmp(key, "Device")) { free(n->device_path); n->device_path = iwd_props_str_dup(v); } else if (!strcmp(key, "Device")) { free(n->device_path); n->device_path = iwd_props_str_dup(v); }
else if (!strcmp(key, "KnownNetwork")) { free(n->known_path); n->known_path = iwd_props_str_dup(v); } else if (!strcmp(key, "KnownNetwork")) { free(n->known_path); n->known_path = iwd_props_str_dup(v); }
} }
@ -79,25 +84,50 @@ iwd_network_free(Iwd_Network *n)
free(n); free(n);
} }
/* Reply context captures the *manager* (which outlives all sub-objects) and /* Reply context captures the *manager* (which outlives all sub-objects) plus
* a strdup'd SSID, never the Iwd_Network the network may disappear from * a strdup'd SSID and object path, never the Iwd_Network the network may
* the next scan before iwd's reply lands, and a raw back-ref would UAF. */ * disappear from the next scan before iwd's reply lands, and a raw back-ref
* would UAF. The path is re-resolved through the live hash at reply time. */
typedef struct typedef struct
{ {
Iwd_Manager *m; Iwd_Manager *m;
char *ssid; char *ssid;
char *path;
} _Net_Reply_Ctx; } _Net_Reply_Ctx;
static Iwd_Network *
_ctx_resolve(const _Net_Reply_Ctx *ctx)
{
if (!ctx->m || !ctx->path) return NULL;
const Eina_Hash *h = iwd_manager_networks(ctx->m);
return h ? eina_hash_find(h, ctx->path) : NULL;
}
static void static void
_on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) _on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{ {
_Net_Reply_Ctx *ctx = data; _Net_Reply_Ctx *ctx = data;
Iwd_Network *n = _ctx_resolve(ctx);
const char *en, *em; const char *en, *em;
if (eldbus_message_error_get(msg, &en, &em) && ctx->m) if (eldbus_message_error_get(msg, &en, &em))
iwd_manager_report_error(ctx->m, {
"Connect to '%s' failed: %s", /* iwd only fails Connect() once the attempt is truly over — wrong
ctx->ssid ? ctx->ssid : "?", em ? em : en); * passphrase, out of range, etc. Mark the row failed (!) rather than
* leaving a misleading behind. */
if (n) n->conn_status = IWD_CONN_FAILED;
if (ctx->m)
iwd_manager_report_error(ctx->m,
"Connect to '%s' failed: %s",
ctx->ssid ? ctx->ssid : "?", em ? em : en);
}
else
{
if (n) n->conn_status = IWD_CONN_IDLE;
/* report_error notifies on the failure path; do it here for success. */
if (ctx->m) iwd_manager_notify(ctx->m);
}
free(ctx->ssid); free(ctx->ssid);
free(ctx->path);
free(ctx); free(ctx);
} }
@ -111,6 +141,7 @@ _on_forget_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_U
"Forget '%s' failed: %s", "Forget '%s' failed: %s",
ctx->ssid ? ctx->ssid : "?", em ? em : en); ctx->ssid ? ctx->ssid : "?", em ? em : en);
free(ctx->ssid); free(ctx->ssid);
free(ctx->path);
free(ctx); free(ctx);
} }
@ -134,6 +165,7 @@ _reply_ctx_new(Iwd_Network *n)
if (!ctx) return NULL; if (!ctx) return NULL;
ctx->m = n->manager; ctx->m = n->manager;
ctx->ssid = n->ssid ? strdup(n->ssid) : NULL; ctx->ssid = n->ssid ? strdup(n->ssid) : NULL;
ctx->path = n->path ? strdup(n->path) : NULL;
return ctx; return ctx;
} }
@ -141,6 +173,10 @@ void
iwd_network_connect(Iwd_Network *n) iwd_network_connect(Iwd_Network *n)
{ {
if (!n || !n->proxy) return; if (!n || !n->proxy) return;
/* Show progress immediately and clear any prior failure marker; the reply
* (success/failure) and the Connected property settle the final state. */
n->conn_status = IWD_CONN_CONNECTING;
if (n->manager) iwd_manager_notify(n->manager);
/* Network.Connect() takes no args; iwd will dial the registered Agent /* Network.Connect() takes no args; iwd will dial the registered Agent
* for a passphrase if needed. */ * for a passphrase if needed. */
eldbus_proxy_call(n->proxy, "Connect", _on_connect_reply, eldbus_proxy_call(n->proxy, "Connect", _on_connect_reply,

View file

@ -13,6 +13,16 @@ typedef enum {
IWD_SEC_UNKNOWN, IWD_SEC_UNKNOWN,
} Iwd_Security; } Iwd_Security;
/* Connection attempt status, tracked locally from our own Connect() call so
* the row can show progress and failure. iwd's Network.Connect() only returns
* once the attempt concludes, so its reply is authoritative Network.Connected
* may flicker true mid-handshake and must not be trusted on its own. */
typedef enum {
IWD_CONN_IDLE, /* not attempting; ✔ iff Network.Connected */
IWD_CONN_CONNECTING, /* Connect() in flight */
IWD_CONN_FAILED, /* last Connect() returned an error (e.g. bad PSK) */
} Iwd_Conn_Status;
typedef struct _Iwd_Network Iwd_Network; typedef struct _Iwd_Network Iwd_Network;
struct _Iwd_Network struct _Iwd_Network
@ -23,6 +33,7 @@ struct _Iwd_Network
char *known_path; char *known_path;
Iwd_Security security; Iwd_Security security;
Eina_Bool connected; Eina_Bool connected;
Iwd_Conn_Status conn_status;
/* Signal strength in 100*dBm units (iwd convention). Valid iff have_signal. */ /* Signal strength in 100*dBm units (iwd convention). Valid iff have_signal. */
int16_t signal_dbm; int16_t signal_dbm;