Skip to content

smp-server: support namespaces#1784

Open
shumvgolove wants to merge 35 commits into
masterfrom
sh/smp-namespace
Open

smp-server: support namespaces#1784
shumvgolove wants to merge 35 commits into
masterfrom
sh/smp-namespace

Conversation

@shumvgolove

Copy link
Copy Markdown
Collaborator

No description provided.

@shumvgolove shumvgolove force-pushed the sh/smp-namespace branch 3 times, most recently from a6b960b to 236e69d Compare May 22, 2026 11:26
- Reject IPv6 aliases of 169.254.169.254 (IPv4-compatible / IPv4-mapped /
  6to4 / NAT64) via numeric range check on parsed IPv6.
- Disable HTTP redirects on the Eth RPC request.
- Restrict SimplexName labels to ASCII (Cyrillic/Greek/full-width otherwise
  hash to different on-chain records and diverge from UTS-46 registrars).
- pingEndpoint: only JsonRpcErr means "reachable"; transport/decode failures
  fail startup. boundedIniInt: readMaybe over partial read.
- Add 127.0.0.0/8 and 0.0.0.0 to isLoopback.
- Replace hand-rolled hex helpers with Data.ByteArray.Encoding; raise
  managerConnCount to match rpcMaxConcurrency; hex Show for NameOwner.
- Fuse parallel http/https when into unless+case; drop reverse/re-reverse
  in mkDomain TLDWeb; first AbiInvariantViolated; Nothing <$ decodeAddress;
  forM_ (eitherToMaybe ...); >>= chain in NameOwner FromJSON.
- Drop dead imports/exports/pragmas and two restating comments.
- Tests: factor unsafeOwner/unsafeLink, addr1/2/3, testNamesConfig; add
  non-ASCII label rejection coverage.
The bare-name fallback and bareDomain parser would otherwise consume
arbitrarily many non-space bytes via takeWhile1 before any validation
or length check. A crafted multi-megabyte token would be decoded as
UTF-8 and re-parsed in full before being rejected.

Introduce `boundedNonSpace` (scan with 253-byte cap) at the two
takeWhile1 sites. Inputs longer than 253 bytes leave residue that
parseOnly's implicit endOfInput rejects, so the parser fails fast
without ever allocating the full input.

The bound is the DNS full-domain limit, chosen for being a familiar
ceiling generous enough to cover any realistic SimpleX name (longest
plausible @user.subdomain.simplex stays well under 100 bytes). No
per-label cap — SimpleX names don't go through DNS label resolution
and there's no semantic reason to constrain individual labels.
shumvgolove and others added 4 commits June 8, 2026 11:37
…out auth)

validateUrl gains two operator-friendly relaxations and a regression test:

- Allow a path prefix (e.g. https://gw.example.com:443/snrc) for a resolver
  behind a reverse-proxy sub-path; /resolve/<name> and /health are appended
  (HttpResolver already strips one trailing slash, so root and sub-path
  behave identically). Query/fragment/userinfo stay rejected.

- Off-loopback, reject only http WITH resolver_auth (the Authorization header
  would travel in cleartext). http without auth is now allowed (no secret to
  leak; resolver data is public — also lets dev setups reach a host resolver
  via http://host.docker.internal). https is always allowed, with or without
  auth. Plain http has no response integrity; intended for trusted/local
  networks only.

Exports validateUrl and adds validateUrlSpec (11 cases) to SMPNamesTests.
RSLV collapsed every non-hit (no resolver, malformed name, not found,
backing-store failure) to ERR AUTH, so a client iterating its configured
servers could not tell "this router has no resolver, try the next" from
"name not registered, stop", and a transient backend error read as an
authoritative miss.

Names capability is runtime config, orthogonal to the linear SMP version
(a future v21 router without [NAMES] must still advertise v21), so it is
signalled by a command-time error like allowSMPProxy, not by the version
range:

  no resolver configured -> ERR CMD PROHIBITED  (client skips, tries next)
  backing-store failure   -> ERR INTERNAL        (transient: retry/surface)
  not found / malformed   -> ERR AUTH            (authoritative "no such name")

Update the protocol spec error table and add agent tests for the
no-resolver (CMD PROHIBITED) and backend-failure (INTERNAL) paths.
Comment on lines +160 to +165
\# Operator runs the resolver alongside smp-server (default port 8000)\n\
\# with its own Ethereum JSON-RPC endpoint configured in resolver.toml.\n\
\# Co-locating with the proxy role logs a startup advisory: slow RSLV calls can\n\
\# serialise other forwarded commands on the same proxy-relay session.\n\
\# For high-volume deployments, run [NAMES] on a separate host.\n\
\# Restart required to change settings.\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is wrong and should not be the case

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, we don't colocate it with SMP, right?

Comment on lines +135 to +137
else case J.eitherDecodeStrict (BL.toStrict bs) of
Left e -> pure (Left (InvalidJson e))
Right v -> pure (Right v)

@evgeny-simplex evgeny-simplex Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else case J.eitherDecodeStrict (BL.toStrict bs) of
Left e -> pure (Left (InvalidJson e))
Right v -> pure (Right v)
else first InvalidJson <$> J.eitherDecodeStrict (BL.toStrict bs)

@evgeny-simplex evgeny-simplex Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general, please review and refactor such trivial cases into forM, mapM, first, second, bimap, maybe, etc.


doGet :: ResolverEnv -> Text -> IO (Either ResolverError J.Value)
doGet ResolverEnv {manager, baseUrl, authHdr, timeoutMicro, maxResponseBytes} path = do
req0 <- parseRequest (T.unpack (baseUrl <> path))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this T.unpack is a smell that incorrect type is passed from upstream

Comment on lines +140 to +145
-- | Percent-encode a name component (path-safe). Aggressive: encode every
-- byte that isn't an unreserved character per RFC 3986. The resolver expects
-- raw labels (e.g., `alice.simplex`); slashes and other ASCII punctuation
-- would change the request path semantics if passed through verbatim.
percentEncode :: Text -> Text
percentEncode = decodeLatin1 . urlEncode True . encodeUtf8

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial function that is not needed if you use correct types (String or ByteString)

-- and the smpEncoded NameRecord is <= its JSON body, so capping
-- the body here guarantees the response always frames. An
-- over-cap body fails as BodyTooLarge -> ERR (NAME (RESOLVER ..)).
resolverMaxResponseBytes = boundedIniInt 16000 1024 16000 "resolver_max_response_bytes"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and in some other place it's 64kb and no 16. If we need larger size, we could use zstd compression in SMP protocol.

Comment thread src/Simplex/Messaging/Server/Main.hs Outdated
other -> Left $ "unexpected port syntax: " <> other
unless (null (uriQuery uri)) $ Left "query string not allowed (it does not compose with the appended /resolve/<name> path)"
unless (null (uriFragment uri)) $ Left "fragment not allowed (fragments are never sent to the server)"
-- A path prefix is allowed and used as the base for /resolve/<name> and

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is very complex and unreadable, needs to be simplified

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use library for URI parsing, why we are re-implement it here?

Comment thread src/Simplex/Messaging/Server/Main.hs
Comment thread src/Simplex/Messaging/Server/Main.hs
Comment thread src/Simplex/Messaging/Protocol.hs Outdated
Comment on lines +1606 to +1607
| -- | no name-resolving servers configured (agent-originated only)
NO_SERVERS

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as it's agent-originated, it must not be in this type - it must be in Agent errors

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is only for errors returned by the server, it can't contain errors reported upstream

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
= -- | the names role / resolver is not configured on this server
NO_RESOLVER
| -- | the name is not registered (resolver returned not-found)
NO_NAME

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT_FOUND would be more typical

There may also be expired error - I don't know how it will be reported by resolver, so probably not needed for now.

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
Comment on lines +1947 to +1948
-- Name is validated at parse (invalid syntax fails here -> CMD error),
-- so the handler only ever sees a valid SimplexNameDomain.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- Name is validated at parse (invalid syntax fails here -> CMD error),
-- so the handler only ever sees a valid SimplexNameDomain.

redundant

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
PRXY host auth_ -> e (PRXY_, ' ', host, auth_)
PFWD fwdV pubKey (EncTransmission s) -> e (PFWD_, ' ', fwdV, pubKey, Tail s)
RFWD (EncFwdTransmission s) -> e (RFWD_, ' ', Tail s)
-- Version gating is the client's job (Client.hs), not the encoder's.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- Version gating is the client's job (Client.hs), not the encoder's.

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
PFWD fwdV pubKey (EncTransmission s) -> e (PFWD_, ' ', fwdV, pubKey, Tail s)
RFWD (EncFwdTransmission s) -> e (RFWD_, ' ', Tail s)
-- Version gating is the client's job (Client.hs), not the encoder's.
RSLV d -> e (RSLV_, ' ', Tail (strEncode d))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RSLV d -> e (RSLV_, ' ', Tail (strEncode d))
RSLV d -> e (RSLV_, ' ', d)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I am missing something, but using Tail explicitly here is redundant - all it achieves is preventing length prefixing of the string. Directly using d here achieves the same.

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
-- so the handler only ever sees a valid SimplexNameDomain.
CT SResolver RSLV_ -> do
Tail bs <- _smpP
either fail (pure . Cmd SResolver . RSLV) (strDecode bs)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
either fail (pure . Cmd SResolver . RSLV) (strDecode bs)
Cmd . SResolver . RSLV <$> _smpP

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
PONG -> e PONG_
-- Field-ordered Encoding NameRecord (no JSON on the wire); a response that
-- arrived is already on a supported version, so no version gate.
RNAME rec -> e (RNAME_, ' ', rec)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RNAME rec -> e (RNAME_, ' ', rec)
RNAME rec -> e (RNAME_, ' ', Tail $ toStrict $ J.encode rec)

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
OK_ -> pure OK
ERR_ -> ERR <$> _smpP
PONG_ -> pure PONG
RNAME_ -> RNAME <$> _smpP

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RNAME_ -> RNAME <$> _smpP
RNAME_ -> fmap RNAME . J.decodeStrict . unTail <$?> _smpP

Comment thread src/Simplex/Messaging/Protocol.hs Outdated
smpEncode = \case
NO_RESOLVER -> "NO_RESOLVER"
NO_NAME -> "NO_NAME"
NO_SERVERS -> "NO_SERVERS"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

Comment thread src/Simplex/Messaging/Server.hs Outdated
-- error that reaches the client as ERR (NAME ...).
(selector, msg) <- asks namesEnv >>= \case
Nothing -> pure (rslvDisabled, ERR $ NAME NO_RESOLVER)
Just nenv -> liftIO (resolveName nenv d) <&> \case

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's the crux of the problem. You call resolver inline, blocking further command processing (and that's what all those comments were about). This has to use the same pattern as we use to create proxy session - it must be processed asynchronously, and response send from forked thread. The function simply returns Nothing as command response, deferring response to forked thread.

SNRC names resolver role: RSLV command -> HTTP resolver -> RNAME record.
Agent owns server selection (ServerRoles.names); NAME error family; async,
concurrency-bounded resolution; length-prefixed extensible wire; spec.
Comment thread src/Simplex/Messaging/Server.hs Outdated
Comment thread src/Simplex/Messaging/Server.hs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants