Skip to content

lanlink: known limitations while in maintenance mode (tracking) #666

Description

@openwong2kim

Tracking issue for lanlink design-level defects that are known, analysed, and deliberately not scheduled. Consolidates #663 and #664 so the backlog reflects the actual plan rather than implying this work is queued.

Why these are not being fixed

lanlink is in maintenance mode. P1/P2 bugs get fixed — #658 did, in #662, and the underlying write failure will be fixed so pairing actually succeeds. What it does not get is further protocol work, because peer messaging is moving onto tailnet transport.

The reasoning: wmux already ships wmux web --tailscale, an 8-character pairing code, and a versioned phone API — the phone already connects that way. Machine-to-machine over a hand-rolled PAKE is the odd one out, and it means owning encryption, replay defence, a secret store, and netsh firewall rules for what is ultimately "deliver a text message to an inbox." #658 is the argument in practice: the reporters were on a WireGuard overlay the whole time — they already had an authenticated encrypted network and were bitten by our custom one layered on top. On one of their hosts the netsh rule application had failed outright.

Every limitation below is dissolved by that transition rather than fixed in place. Nothing is removed until a working replacement exists, and the lanlink.* RPC surface keeps working. The message-level contract (msg.text, state.update, the durable inbox) is transport-independent by construction, so it carries over.


1. A revoked or burned peer returns after a restart (was #663)

PeerStore.revoke() and noteSteadyStateAuthFail() intentionally keep their in-memory effect when persist() throws — rolling them back would be fail-open, restoring access the user just removed and stopping a host that cannot persist from ever reaching PEER_BURN_THRESHOLD. #662 makes that explicit.

But in-memory only means not durable, and the restore is active rather than passive: atomicWriteJSONSync renames the previous primary to .bak before writing, and atomicReadJSONSync falls back to .bak when the primary is missing or invalid. So on a host where the peer file cannot be written:

  1. user revokes a peer (or it burns past the threshold)
  2. persist() throws; the revocation holds in memory; the RPC surfaces the error
  3. daemon restarts
  4. load() finds no valid primary and falls back to .bakthe pre-revocation generation
  5. the peer is active again, secret intact

pinFailCount / burned rewind the same way.

Proper fix: restrictions need durability independent of the peer file — an append-only revocation journal re-applied over whatever load() produces, including a .bak result. A revocation should also be cheap to persist (a peer id, not the whole store) so it does not go through the write path that is failing.

Scope note: triggers only on a host where persist() already fails, which is the #658 population.

2. The mirror-image half-pair: joiner fails to save (was #664)

#662 makes the responder's commit atomic, so a failed save there leaves neither side paired. The opposite direction is still open.

pairWithPeer finishes the handshake and calls opts.peers.upsertPaired(result) as its last step. If that throws — same causes as #658 — the joiner rolls back cleanly and reports failure, but the responder committed and confirmed several steps earlier and has no idea. Responder holds a peer the joiner has no secret for: the #658 signature with roles swapped, equally unrecoverable.

Reordering the responder to commit after sending its confirmation does not help; it relocates the window. A pairing is only genuinely mutual once both sides have committed, and no single-round-trip ordering gives you that.

Proper fix: three-way confirm — the responder holds the pairing pending and promotes it only after the joiner confirms over the established AEAD channel, dropping unconfirmed pendings on close. A cheaper approximation is reconciling on the first RECONNECT_HELLO miss.

Known doc inaccuracy: the #662 CHANGELOG says "neither side is left holding a pairing the other cannot honour." True for the responder-fails case it fixes, not for this one. Correct when this is addressed.


Still actively worked

Related: #659 (control-pipe multiplexing, documented in #662).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low priority — nice to have

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions