Hubpoint - #405
Open
awehttam wants to merge 25 commits into
Open
Conversation
Lets BinktermPHP distribute echomail to subordinate FTN nodes and points (boss.point addresses), not just receive from uplinks. Adds hub_nodes/hub_node_areas/hub_node_outbound tables, HubNodeManager (CRUD + area subscriptions), HubFanout (SEEN-BY/PATH-aware fanout into the outbound queue), and an admin UI at /admin/hub-nodes (labeled "Downlinks" to avoid confusion with upstream uplinks). This is enqueue-only per docs/proposals/HubPointSystemJuly2026.md Phase 1 - actual binkp delivery of queued packets is Phase 2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Delivers the packets Phase 1 enqueued into hub_node_outbound, both pull and push: - BinkpSession: M_ADR matching and password lookup now also check hub_nodes (fixes a real bug where a multi-AKA peer could get the wrong address selected as remoteAddress). New sendHubNodeOutbound() serves pending hub_node_outbound rows to the authenticated peer, mirroring the existing sendFreqFiles()/sendHoldFiles() pattern. - BinkpClient: connect()'s existing non-uplink fallback now checks hub_nodes (inet_host/port/session_password) before nodelist/DNS, so push delivery works through existing address-based call paths with no protocol/IPC changes. New pollAllHubNodes(). - binkp_poll.php: new --all-hub-nodes flag; a plain positional address already resolves hub nodes automatically via the connect() change. - Scheduler: new runScheduledHubNodePush(), modeled on the existing crashmail-poll pattern, wired into the daemon loop. Verified live end-to-end: a raw CRAM-MD5 binkp client simulating a hub node authenticated against hub_nodes and received its queued packet over the wire from a real binkp_server.php instance. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Inbound: netmail addressed to a registered hub node/point (not to us) is now forwarded into hub_node_outbound as transit mail instead of being dropped as undeliverable, gated behind HUB_ROUTE_NETMAIL (off by default). Outbound: fixes a real bug where netmail composed by a local user and addressed to a registered downlink (e.g. 227:1/400.1) got misrouted through an unrelated uplink's network-pattern wildcard match instead of going to the downlink directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The global security.allow_plaintext_fallback policy (hardened for uplink/public-FTN security) was also blocking plaintext auth for downlink/point connections. Registered hub nodes now always accept plaintext in addition to CRAM-MD5 regardless of that policy, since downlinks are systems under our own administrative control, not the public network the policy is meant to guard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Downlink Queue viewer: new tab on the /binkp status page listing hub_node_outbound rows (metadata only) with a packet inspector reusing the existing queue-viewer modal. Extracted the packet-parsing logic shared by both into src/Binkp/Protocol/PacketInspector.php so the CLI test client below can reuse the exact same dump format instead of a third duplicate implementation. binkp_test_client.php: switched to the real BinkpFrame class for wire framing, added CRAM-MD5 support (--no-cram to force plaintext), fixed file reception (previously M_SKIPed everything the server sent - now receives, sends M_GOT, and dumps .pkt contents), added --compose-netmail to send a real test netmail without hand-crafting a packet file, and fixed EOB handling to always reply and wait for the peer to close rather than terminating on the first round-trip. Bounded EOB replies to avoid an infinite ping-pong discovered during testing against a peer (BinkpSession) that also always-replies with no cap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three related bugs found via live testing with binkp_test_client.php:
- Netmail from a registered point/downlink addressed to a third party
(neither us nor another downlink) was silently swallowed into the
local sysop's inbox instead of being relayed, because
findTargetUser()'s name-based fallback matching ("To: sysop") never
checked whether the destination address actually belonged to us.
Added HubNetmailRouter::relayIfFromHubNode(), which queues such mail
into the normal uplink-routed outbound flow, and gated the name
fallback strategies on the destination actually being one of our own
AKAs.
- Relayed/transit messages (echomail fanout and all three netmail hub
routing paths) got a second PID kludge and tearline stacked on top
of the original message's, because BinkdProcessor::writeMessage()
unconditionally regenerates both. Added a skip_default_pid_tearline
flag, set when the preserved kludges already carry a PID line.
- Relayed messages had no Via line recording the hop, and any existing
Via history from earlier hops was being dropped rather than
preserved. Added generateViaLine() plus
EchomailSeenBy::parseViaLines()/formatViaLines() and wired them into
every hub relay/delivery path (Via lines accumulate per hop, unlike
PID which stays singular to the true originator).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
handleSentFileConfirmation() assumed any sent filename it didn't recognize as an "extra" file lived in data/outbound/, but sendHubNodeOutbound() writes its packets to a temp path and deletes them right after sending. Register the temp file into the same extraOutboundFilesByName lookup .req files use (without going through addExtraFile(), which would also queue it for a duplicate send via sendFiles()) so the eventual M_GOT confirmation resolves correctly instead of logging a misleading warning for a delivery that actually succeeded. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oints Delivering netmail addressed to a registered downlink was gated behind a global env flag that defaulted off, unlike the other two HubNetmailRouter directions. A real reply to a registered point got silently dropped as undeliverable because nothing had set the flag. This is core "deliver mail addressed to a registered downlink" functionality, not an opt-in relay feature, so it's now gated solely by the existing per-node allow_inbound_netmail flag, matching how routeOutboundIfHubNode() and relayIfFromHubNode() already worked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
openHubNodeModal() correctly set the dropdown to the point's real boss_address, but the onHubNodeTypeChange() call right after it unconditionally repopulated the same dropdown with no selection, resetting it to the browser's default first option (the system's primary AKA) every time the edit modal opened. Preserve the current selection across the repopulate instead of dropping it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cate kludges storeEchomail() only ever fanned messages out to other downlinks, never forwarded them up to the echoarea's configured uplink - a point posting echomail was only ever seen locally and by other downlinks, never by the real network. Adds relayEchomailToUplinkIfNeeded(), guarded against loops via direct origin-vs-uplink comparison and a SEEN-BY containment check. Reusing spoolOutboundEchomail()/writeMessage() for already-received messages (rather than freshly-composed local posts, its only prior use) exposed several duplicate-kludge bugs: a stored AREA: line getting echoed back on top of the freshly-built one, and a relayed message's own tearline/Origin/SEEN-BY/PATH getting duplicated by unconditional synthesis in writeMessage(). Also reverts an earlier attempt to add Via lines to relayed echomail: Via (FSC-0043) is netmail-only bookkeeping - echomail already tracks hop history via PATH, and real tossers like HPT never stamp Via onto it.
New --compose-echomail flag (paired with --area/--domain) builds and sends a test echomail packet, matching the existing --compose-netmail flow. Useful for exercising hub/downlink echomail delivery without a real point client.
bg-light text-dark rendered dark-on-dark under the dark theme; bg-secondary is theme-safe and already used elsewhere on this page.
…ce.php hub_node_outbound had no cleanup at all - rows (including their packet_data bytea blobs) accumulated forever regardless of status, and the per-node queue_retention_days column was configurable but never actually enforced anywhere. Adds a maintenance step that deletes sent/failed rows once they're older than their node's own retention setting (measured from sent_at when set, else created_at); pending/held rows are left alone regardless of age.
…INTENANCE Adds docs/Downlinks.md as the user-facing reference for the hub/point system: node vs. point registration, area subscriptions, echomail/netmail distribution, delivery, and the outbound queue viewer/cleanup. Links it from docs/index.md's FTN Networking section, adds the feature summary to docs/UPGRADING_1.10.0.md, and adds the new queue-purge maintenance step to docs/MAINTENANCE.md.
…e 4, correct commit status Phase 4 (previously Areafix) is now TIC / File Area Distribution, with a full design: a new hub_node_fileareas subscription table, hub_node_outbound extended with file_id/tic_file_data/tic_filename and a 'tic' message type, and fanout/uplink-relay/delivery mirroring the existing echomail pattern. Areafix moves to Phase 5 and is expanded to Areafix/FileFix to cover both echo and file area self-service subscription management. Also corrects Phase 2 and Phase 3's status - both were still marked "uncommitted" despite being committed (7ad64d7, and 0e4c6a7 plus follow-on fixes 4ddc552/b17f0398, respectively).
The wire filename for queued hub_node_outbound packets was generated
with uniqid('', true), producing names like
hubnode_6a76b13aaf26a9.35500206.pkt that break on mailers expecting
classic FTN 8.3 packet names.
Extends the hub distribution system to file areas: per-downlink file area subscriptions, TIC fanout with Seenby-based loop prevention, uplink relay for files received from a downlink/point, TIC-pair delivery over binkp, admin UI, and Downlink Queue viewer support. Also restricts the file area subscription checklist to public areas only, excluding private ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ork domain A point's boss AKA (or a node's own address) now resolves to a single network domain via HubNodeManager::resolveDomain(), and %LIST/+TAG/-TAG in HubAreafixProcessor only match echoareas/fileareas in that domain. Prevents a point from subscribing across domains it has no business seeing, and fixes a latent bug where area lookup ignored domain entirely and could match the wrong same-tag area from a different network. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds dedicated areafix_password/filefix_password columns on hub_nodes, separate from the binkp session_password so a subordinate's Areafix/ Filefix credential can be rotated independently. Wires up i18n strings, admin hub_nodes UI, and BinkdProcessor/HubNetmailRouter routing needed for HubAreafixProcessor's server-side robot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
getAreaSubscriptions()/getFileAreaSubscriptions() previously listed every active area regardless of domain, letting an admin subscribe a node/point to areas outside the network its boss address actually belongs to (and, per this DB's data, to the wrong one of two same-tag areas in different domains). Both now resolve the node's domain via resolveDomain() and only offer areas in that domain - already-subscribed areas stay visible regardless of domain so a save doesn't silently drop them, since bulkSet*Subscriptions() fully replaces the set from whatever the checklist returns. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
%RESCAN [AREATAG] [days] re-queues historical echomail into hub_node_outbound for a hub node's subscribed area(s), going back a given number of days (default 182 / max 3650, matching Mystic's ~6-month default). With no area tag it covers every currently subscribed area; with one, just that area (must already be subscribed). Tag and day count can appear in either order. HubFanout::rescanForNode() always resends regardless of SEEN-BY, since a rescan is a deliberate re-request rather than a fresh toss - unlike live fanout(), which skips a downlink already present in a message's SEEN-BY to avoid loops. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the command table, design notes, implementation checklist, and New/Modified Files tables in docs/proposals/HubPointSystemJuly2026.md, plus the AreaFix/FileFix command reference and a short usage note in docs/Downlinks.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rename admin menu items to "BinkP Uplinks" and "BinkP Downlinks" for clarity, move Networks above BinkP Uplinks in the admin dropdown, and rename the /binkp status page's Queues/Kept Packets tabs to "Uplink Queues"/"Uplink Kept Packets". Updated across all locale catalogs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… time Previously each pending hub_node_outbound row was sent to a downlink as its own .pkt file. Add a message_payload JSONB column that stores the createOutboundPacket()-ready message array at queue time, and have BinkpSession::sendHubNodeOutbound() decode every pending row's payload for a connecting node and combine them into a single multi-message packet at send time, reducing per-session packet count. TIC rows and rows queued before this column existed are unaffected (sent individually as before). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nds, and cap bundle size - scripts/binkp_server.php: require src/functions.php so generateTzutc() is defined (was undefined for inbound BinkP sessions) - hub_nodes.compress_outbound: opt-in per-downlink ZIP compression of bundled outbound echomail/netmail, with a hard fallback to the raw .pkt if ZipArchive is unavailable or archive creation fails - BinkpSession/Scheduler: retry hub_node_outbound rows left 'failed' by an interrupted session on the next connection (in or out), capped at 10 attempts so a broken row doesn't retry forever - BinkpSession: split bundled outbound into multiple .pkt files capped at hub_nodes.max_packet_kb instead of one unbounded bundle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.