From 33c458ffd448ae42c9f2275bac316d28f4f8f29e Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:23:44 +0000 Subject: [PATCH 1/3] agent: report PRXY broker errors as proxy-to-relay errors (#1842) * agent: report PRXY broker errors as proxy-to-relay errors The proxy returns PROXY BROKER errors only when it fails to connect to the destination relay, but for PRXY they were mapped to SMP (PROXY ...), losing the relay address, so the clients reported them as errors of the connection to the forwarding server. Map them to PROXY {proxyServer, relayServer, ...}, the same shape PFWD errors already use. * Apply suggestion from @epoberezkin * agent: keep ProxyProtocolError for PRXY broker errors Reverts 826a2377. ProxyResponseError wraps PCEResponseError, a response that failed to parse, and both clients match protocolError when rendering this error, so the connect alert falls through to the raw error dump - and released clients cannot render it at all. temporaryAgentError and serverHostError also match ProxyProtocolError only, so the failure stops being retried and proxy fallback no longer engages. --------- Co-authored-by: Evgeny --- ...026-08-05-proxy-relay-error-attribution.md | 81 +++++++++++++++++++ src/Simplex/Messaging/Agent/Client.hs | 14 +++- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 plans/2026-08-05-proxy-relay-error-attribution.md diff --git a/plans/2026-08-05-proxy-relay-error-attribution.md b/plans/2026-08-05-proxy-relay-error-attribution.md new file mode 100644 index 000000000..63203ec11 --- /dev/null +++ b/plans/2026-08-05-proxy-relay-error-attribution.md @@ -0,0 +1,81 @@ +## Root cause: PRXY errors are attributed to the forwarding server instead of the destination relay + +When private routing is enabled and the destination relay is unreachable, the client reports +**"Error connecting to forwarding server smp5.simplex.im"** — naming a preset server that the client +connected to successfully. Retrying rotates to the next proxy (`getNextServer`, `Agent/Client.hs:689`) +and produces the same message with a different preset server, so the destination server is never named +and the failure looks like an outage of our own infrastructure. + +### Reproduction + +Connecting to a contact address on an unresolvable host (`simplex.server.home`, no DNS record): + +``` +-- private routing off (correct) +BROKER {brokerAddress = "smp://VvXX…@simplex.server.home:5223", + brokerErr = NETWORK {networkError = NEConnectError {connectError = "…does not exist (Name or service not known)"}}} + +-- private routing on (misattributed) +SMP {serverAddress = "smp://…@smp5.simplex.im,…onion", + smpErr = PROXY {proxyErr = BROKER {brokerErr = NETWORK {networkError = NEFailedError}}}} +``` + +### The asymmetry between the two proxied paths + +A server returns `PROXY (BROKER …)` only from `smpProxyError` (`Client.hs:804-815`), which is called +exclusively where the proxy failed to reach the relay — `PRXY` (`Server.hs:1444`) and `PFWD` +(`Server.hs:1466`). The error therefore *always* describes the proxy→relay hop. The two paths then +diverge in how the agent wraps it: + +**PFWD — keeps both addresses** (`Agent/Client.hs:1183-1189`): the proxy's error arrives as +`Left ProxyClientError` and is thrown as `PROXY {proxyServer, relayServer, proxyErr}`. + +**PRXY — drops the relay** (`Agent/Client.hs:713`): `connectSMPProxiedRelay` has no `Either` layer, so +the error arrives as `PCEProtocolError` and `liftClient SMP` maps it to `SMP (PROXY …)` +(`Agent/Client.hs:1244`). The destination address is discarded. + +Both clients read the second shape as a client→proxy failure and word it accordingly +(`SimpleXAPI.kt:2692`, `ErrorAlert.swift:117`), which is never what it means. + +### Fix + +In `newProxiedRelay`, map proxy-reported `PROXY (BROKER …)` errors to the same shape `PFWD` already +produces: + +```haskell +proxyRelayError :: HostName -> ErrorType -> AgentErrorType +proxyRelayError proxyHost = \case + e@(SMP.PROXY (SMP.BROKER _)) -> PROXY {proxyServer = protocolClientServer smp, relayServer = …destSrv, proxyErr = ProxyProtocolError e} + e -> SMP proxyHost e +``` + +`liftClient` applies this only to `PCEProtocolError`, so genuine client↔proxy failures (response +timeout, network error, proxy transport version) still map to `BROKER …` and remain attributed +to the proxy. Both apps already render the resulting shape correctly, with no client change: +*"Forwarding server smp5.simplex.im failed to connect to destination server simplex.server.home."* + +The guard is `BROKER` rather than every `ProxyError`, so the remap covers exactly the misattributed +class and nothing else. `BASIC_AUTH` is deliberately excluded — the proxy returns it when proxying is +disabled or the basic auth does not match (`Server.hs:1416-1420`), which is a client↔proxy fact and is +correctly attributed today. `NO_SESSION` is returned only for `PFWD`. `PROTOCOL` describes the relay +but is not rendered as a proxy-connection error by either client, so leaving it unchanged keeps the +diff to the errors that actually produce a wrong message. + +### Blast radius + +- `temporaryAgentError` (`Agent/Client.hs:1572-1580`) and `serverHostError` (`:1594-1596`) already match + both shapes with the same helpers — retry and proxy-fallback behaviour is unchanged. +- `clientServiceError` (`:1268-1273`) has no `PROXY`-shape twin for `BROKER NO_SERVICE`, but both ends + document that case as unreachable (`Client.hs:812`); left as is. +- simplex-chat `Subscriber.hs:1819-1820` handles both shapes; send failures move from `SndErrProxy` to + `SndErrProxyRelay`, i.e. "Destination server error" rather than "Error" — also more accurate. +- `SMP _ (PROXY _)` becomes unreachable, making `smpProxyErrorAlert` in both clients dead code. Removing + it is a follow-up in simplex-chat, not required by this change. + +### Verification + +- Reproduced before/after with a CLI built against this branch: the error now carries + `relayServer = "smp://VvXX…@simplex.server.home:5223"`, and the direct (non-proxied) path is + byte-identical to before. +- `SMPProxyTests`: 45 examples, 0 failures — including `fails when fallback is prohibited` and both + retry tests, which exercise `newProxiedRelay` and the error classification. diff --git a/src/Simplex/Messaging/Agent/Client.hs b/src/Simplex/Messaging/Agent/Client.hs index 718bcc228..b2df59b96 100644 --- a/src/Simplex/Messaging/Agent/Client.hs +++ b/src/Simplex/Messaging/Agent/Client.hs @@ -714,7 +714,7 @@ getSMPProxyClient c@AgentClient {active, smpClients, smpProxiedRelays, workerSeq pure (clnt, sess) newProxiedRelay :: SMPConnectedClient -> Maybe SMP.BasicAuth -> ProxiedRelayVar -> AM (Either AgentErrorType ProxiedRelay) newProxiedRelay (SMPConnectedClient smp prs) proxyAuth rv = - tryAllErrors (liftClient SMP (clientServer smp) $ connectSMPProxiedRelay smp nm destSrv proxyAuth) >>= \case + tryAllErrors (liftClient proxyRelayError (clientServer smp) $ connectSMPProxiedRelay smp nm destSrv proxyAuth) >>= \case Right sess -> do atomically $ putTMVar (sessionVar rv) (Right sess) pure $ Right sess @@ -725,6 +725,18 @@ getSMPProxyClient c@AgentClient {active, smpClients, smpProxiedRelays, workerSeq TM.delete destSess smpProxiedRelays putTMVar (sessionVar rv) (Left e) pure $ Left e + where + -- proxy reports BROKER errors about the relay, not about its own connection, + -- so they include both addresses, same as PFWD errors. + proxyRelayError :: HostName -> ErrorType -> AgentErrorType + proxyRelayError proxyHost = \case + e@(SMP.PROXY (SMP.BROKER _)) -> + PROXY + { proxyServer = protocolClientServer smp, + relayServer = B.unpack $ strEncode destSrv, + proxyErr = ProxyProtocolError e + } + e -> SMP proxyHost e waitForProxiedRelay :: SMPTransportSession -> ProxiedRelayVar -> AM (Either AgentErrorType ProxiedRelay) waitForProxiedRelay (_, srv, _) rv = do NetworkConfig {tcpConnectTimeout} <- getNetworkConfig c From a908de6416cc2452a9aaf6c0cd2a2596bfc8376b Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:27:24 +0000 Subject: [PATCH 2/3] server: prevent concurrent SKEY commands from overwriting queue sender key (#1844) * server: prevent concurrent SKEY commands from overwriting queue sender key * wip --- src/Simplex/Messaging/Server/QueueStore/Postgres.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simplex/Messaging/Server/QueueStore/Postgres.hs b/src/Simplex/Messaging/Server/QueueStore/Postgres.hs index ce1cf25db..8738bee99 100644 --- a/src/Simplex/Messaging/Server/QueueStore/Postgres.hs +++ b/src/Simplex/Messaging/Server/QueueStore/Postgres.hs @@ -342,7 +342,7 @@ instance StoreQueueClass q => QueueStoreClass q (PostgresQueueStore q) where withQueueRec sq "secureQueue" $ \q -> do verify q assertUpdated $ withDB' "secureQueue" st $ \db -> - DB.execute db "UPDATE msg_queues SET sender_key = ? WHERE recipient_id = ? AND deleted_at IS NULL" (sKey, rId) + DB.execute db "UPDATE msg_queues SET sender_key = ? WHERE recipient_id = ? AND deleted_at IS NULL AND (sender_key IS NULL OR sender_key = ?)" (sKey, rId, sKey) atomically $ writeTVar (queueRec sq) $ Just q {senderKey = Just sKey} withLog "secureQueue" st $ \s -> logSecureQueue s rId sKey where From e3d53428a0c5776f9682264a56436ce97bc3eff8 Mon Sep 17 00:00:00 2001 From: Ed Asriyan Date: Sat, 8 Aug 2026 13:27:02 -0700 Subject: [PATCH 3/3] smp server: fix handshake compatibility by moving server info to SMP v21 (#1845) --- protocol/simplex-messaging.md | 3 ++- src/Simplex/Messaging/Transport.hs | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index 872b35afe..be7bd448f 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -1,4 +1,4 @@ -Version 20, 2026-05-25 +Version 21, 2026-07-05 # Simplex Messaging Protocol (SMP) @@ -107,6 +107,7 @@ This document describes SMP protocol version 20. Versions 1-5 are discontinued. - v18: support client notices in BLOCKED error - v19: service subscriptions to messages (SUBS, NSUBS, SOKS, ENDS, ALLS commands) - v20: public namespaces resolver (RSLV command, RNAME response) — direct or forwarded via PFWD +- v21: server public information in handshake ## Introduction diff --git a/src/Simplex/Messaging/Transport.hs b/src/Simplex/Messaging/Transport.hs index aef23ae9f..9c9392c21 100644 --- a/src/Simplex/Messaging/Transport.hs +++ b/src/Simplex/Messaging/Transport.hs @@ -169,7 +169,8 @@ smpBlockSize = 16384 -- 17 - create notification credentials with NEW (7/12/2025) -- 18 - support client notices (10/10/2025) -- 19 - service subscriptions to messages (10/20/2025) --- 20 - server public information in handshake (7/5/2026) +-- 20 - public namespaces resolver, RSLV command (6/20/2026) +-- 21 - server public information in handshake (7/5/2026) data SMPVersion @@ -204,7 +205,7 @@ namesSMPVersion :: VersionSMP namesSMPVersion = VersionSMP 20 serverInfoSMPVersion :: VersionSMP -serverInfoSMPVersion = VersionSMP 20 +serverInfoSMPVersion = VersionSMP 21 minClientSMPRelayVersion :: VersionSMP minClientSMPRelayVersion = VersionSMP 14 @@ -213,10 +214,10 @@ minServerSMPRelayVersion :: VersionSMP minServerSMPRelayVersion = VersionSMP 14 currentClientSMPRelayVersion :: VersionSMP -currentClientSMPRelayVersion = VersionSMP 20 +currentClientSMPRelayVersion = VersionSMP 21 currentServerSMPRelayVersion :: VersionSMP -currentServerSMPRelayVersion = VersionSMP 20 +currentServerSMPRelayVersion = VersionSMP 21 -- Max SMP protocol version to be used in e2e encrypted connection between -- client and server, as defined by SMP proxy. Normally set below the current