Skip to content

bpsh: add SSH-like remote shell over Bundle Protocol - #100

Open
zebastian wants to merge 4 commits into
nasa-jpl:integrationfrom
zebastian:bpsh
Open

bpsh: add SSH-like remote shell over Bundle Protocol#100
zebastian wants to merge 4 commits into
nasa-jpl:integrationfrom
zebastian:bpsh

Conversation

@zebastian

Copy link
Copy Markdown
Contributor

Add bpsh (client) and bpshd (daemon): a remote shell that executes commands on a remote node over BPv7, in the spirit of ssh but built for store-and-forward DTN links. Implements #83.

Protocol (bpsh_proto):

  • CBOR-framed messages carried in bundle payloads: INIT / INIT_ACK, REQ, STDIN_CHUNK / STDIN_EOF, STDOUT / STDERR, CWD, EXIT, ERROR, EXIT_SESSION.
  • Per-session id and monotonic sequence numbers; the client delivers frames in order and treats ERROR as out-of-band.
  • Shared encode/decode, send, receive and attach helpers.

Daemon (bpshd, bpshd_session):

  • One persistent /bin/sh per client, keyed by source EID, so cd, environment and shell functions persist across commands.
  • Split into a per-session object (the shell, its pipes and command execution) and a manager (session table, deferred-bundle queue, receive/dispatch loop).
  • The exit status and the command boundary travel out-of-band on a control fd: each command runs as "{ cmd ; } <&3 4>&-; echo $? >&4" while stdout/stderr stream verbatim, and the daemon finishes the command when it reads the rc. No in-band sentinels, no holdback.
  • stdin forwarding to the remote command; ReqAttendant-based flow control so large output (e.g. du over a big tree) blocks for ZCO space instead of being dropped; a per-command wall-clock timeout (BPSHD_CMD_TIMEOUT, default 300s) and output cap with process-group teardown; and recovery: a fresh INIT aborts a stalled command and starts a new session.

Client (bpsh):

  • Interactive REPL and one-shot mode (-c, exits with the remote rc and forwards non-TTY stdin).
  • Raw-mode line editing (left/right/home/end/delete), command history on the up/down arrows plus a "history" builtin, and a prompt that shows the remote shell's working directory.
  • "exit" closes the session and leaves the REPL cleanly; SIGSEGV / SIGBUS are reported as a clean "lost the local ION node" exit rather than a core dump.

Adds a loopback regression test (tests/bpsh-loopback) and Makefile wiring.

Add bpsh (client) and bpshd (daemon): a remote shell that executes
commands on a remote node over BPv7, in the spirit of ssh but built
for store-and-forward DTN links.  Implements nasa-jpl#83.

Protocol (bpsh_proto):
  - CBOR-framed messages carried in bundle payloads: INIT / INIT_ACK,
    REQ, STDIN_CHUNK / STDIN_EOF, STDOUT / STDERR, CWD, EXIT, ERROR,
    EXIT_SESSION.
  - Per-session id and monotonic sequence numbers; the client delivers
    frames in order and treats ERROR as out-of-band.
  - Shared encode/decode, send, receive and attach helpers.

Daemon (bpshd, bpshd_session):
  - One persistent /bin/sh per client, keyed by source EID, so cd,
    environment and shell functions persist across commands.
  - Split into a per-session object (the shell, its pipes and command
    execution) and a manager (session table, deferred-bundle queue,
    receive/dispatch loop).
  - The exit status and the command boundary travel out-of-band on a
    control fd: each command runs as "{ cmd ; } <&3 4>&-; echo $? >&4"
    while stdout/stderr stream verbatim, and the daemon finishes the
    command when it reads the rc.  No in-band sentinels, no holdback.
  - stdin forwarding to the remote command; ReqAttendant-based flow
    control so large output (e.g. du over a big tree) blocks for ZCO
    space instead of being dropped; a per-command wall-clock timeout
    (BPSHD_CMD_TIMEOUT, default 300s) and output cap with process-group
    teardown; and recovery: a fresh INIT aborts a stalled command and
    starts a new session.

Client (bpsh):
  - Interactive REPL and one-shot mode (-c, exits with the remote rc
    and forwards non-TTY stdin).
  - Raw-mode line editing (left/right/home/end/delete), command history
    on the up/down arrows plus a "history" builtin, and a prompt that
    shows the remote shell's working directory.
  - "exit" closes the session and leaves the REPL cleanly; SIGSEGV /
    SIGBUS are reported as a clean "lost the local ION node" exit
    rather than a core dump.

Adds a loopback regression test (tests/bpsh-loopback) and Makefile
wiring.
@zebastian

Copy link
Copy Markdown
Contributor Author

see also demo video: https://youtu.be/RYLdBJMoW4g

@zebastian

Copy link
Copy Markdown
Contributor Author

the demo video missed some things, here is what i forgot to mention:

  • full session support, multiple clients can connect at the same time each with their own session
  • passthrough of stdout/stderr and exit code
  • chunking of data into bp frames

open / TODO:

  • pod file documentation
  • bpsec integration (important to secure bpshd access and traffic data)
  • ionadmin configuration (timeouts, etc)
  • idle-session reaper

things compared to ssh explicitly missing

  • session negotiation on start
  • full tty / interactive shells (eg for htop, midnight commander, other interactive terminal applications)

This adds the missing documentation for new bpsh/bpshd binaries.

- bpv7/doc/pod1/bpsh.pod: both invocation forms (-h/-l/-c and the
  positional shorthand), REPL vs. one-shot -c mode, stdin
  forwarding, exit-status semantics, NO_COLOR/TERM, and diagnostics.
- bpv7/doc/pod1/bpshd.pod: the listen EID, per-client persistent
  shells, flow-controlled output, the output cap and command
  timeout (BPSHD_CMD_TIMEOUT, default 300s), and diagnostics.
@zebastian

Copy link
Copy Markdown
Contributor Author

pod file documentation now added

dcpagotto pushed a commit to dcpagotto/ION-DTN that referenced this pull request Jun 10, 2026
ltpdeliv guarded the heap-buffer delivery block on
sessionBuf.heapBufferObj alone, which is always non-zero after
openImportSession allocates the buffer.  heapBufferBytes is only
updated when a red-part segment lands in the [0, heapBufferSize)
range (libltpP.c gates the update on bytesForHeap > 0).  If no
such segment is non-redundantly inserted before the block is
queued for delivery, heapBufferBytes stays 0 and the existing
sdr_insert(sdr, buffer, sessionBuf.heapBufferBytes) call hits
XNCHKZERO(!(nbytes == 0 || nbytes > LARGE_BLK_LIMIT)) inside
_sdrmalloc.  The assertion cancels the transaction after non-
reversible modifications, so the lock-owner-died recovery cannot
roll back, and every subsequent sdr_begin_xn in every daemon
asserts -- the whole node wedges until the test harness shuts it
down.

Guard the block on (heapBufferObj && heapBufferBytes > 0).  When
there are no heap bytes to deliver, skip the read / insert /
zco_append_extent triplet entirely; the file-buffer branch below
still runs and delivers the actual data.  The heap buffer itself
is still freed at session close in closeImportSession, so this
does not leak.

Observed in atomic-tiers run nasa-jpl#100 (__sync, ol9, adjacent-contacts)
as the cascade tracked in #1013.  The exact precondition that
leaves heapBufferBytes at 0 on this test is still under
investigation -- nominal segment ordering for the test's bundle/
segment size puts segment 1 in the heap range and populates
heapBufferBytes=560.  Possible triggers include a torn read or
clobber of the field under __sync memory ordering, or an
insertDataSegment redundancy edge case rejecting the first arrival
of segment 1.  This fix addresses the symptom unconditionally;
diagnostic logging of the actual nbytes value at the call site can
be added as a follow-up to nail down the trigger.
dcpagotto pushed a commit to dcpagotto/ION-DTN that referenced this pull request Jun 10, 2026
The Check 8 grep failed on arc-runner-set-u22 __sync (atomic-tiers
run nasa-jpl#100, batch 6) because six ion.log lines slipped past the
exclude filter:

  - "Can't find (DTPC|BSSP|CFDP) database" -- benign shutdown
    chatter when those subsystems were never started
  - Three security-vdb init messages that appear on some ARC pods
    when the keystore is not pre-populated

Same test passed on the other five batch-6 pods in the same run,
so the failure was always a test-side gap, not an ION regression.
Consolidate the two parallel grep -v chains into one
LTP_ERR_EXCLUDE variable for the new entries.
The stdin forwarder thread blocked indefinitely in read(STDIN_FILENO),
so when the remote command exited bpsh kept running until its own stdin
was closed. Poll stdin with a short (200 ms) timeout so the forwarder
periodically re-checks `running`, and have the main thread clear
`running` before joining the forwarder once the remote command has
exited.
…ases

Exercise bpsh as a full-duplex transport for directory synchronization.

Case 6 runs real rsync with bpsh as its remote shell (-e), proving bpsh
can carry rsync's interactive protocol end-to-end, and reports the bundle
cost. It is a feasibility/correctness check only: rsync is O(files) chatty
(~36+ round-trips here even with -W --no-i-r), which is impractical over a
real delay-tolerant link.

Case 7 reimplements the same sync in exactly two request/response
exchanges regardless of file count: roundtrip 1 pulls a per-file SHA-256
manifest from the remote, roundtrip 2 ships the differing/missing files as
one gzipped tar stream that the server extracts in bulk. This is the
DTN-suitable approach. Both cases assert the remote dir becomes identical
and that the unchanged file is skipped.

Add endpoints ipn:1.513-515 to host1.rc for the new cases.
@zebastian

Copy link
Copy Markdown
Contributor Author

@iondev33 @scburleigh
please especially review Case 7:
This shows an efficient differential, compressed directory sync (rsync-like) in exactly two round-trips, independent of file count -> a practical alternative to CFDP for folder sync

In general: bpsh lets ordinary app code drive cross-DTN operations with no DTN-specific logic and no task-specific code on the remote — it needs only bpshd running, then allows to run any shell accessable programs over bpsh's full-duplex channel.

@250MHz

250MHz commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

There was some discussion about this and the concept of a remote shell in general a while ago that I forgot to share here.

We think this is really great, but since it isn't a core issue having to do with potential mission deployment needs (yet at least), we don't have the resources to properly maintain it, so we'll have to pass for now.

In the long run, apart from simple utilities used in tests, it'll be more maintainable if proper applications exist outside of ION's codebase (though this is made difficult by the messy public API that is due for a cleanup). Could you extract bpsh and make it build as its own project?

It's very good to see applications like this though, and we should have a list of references of community utilities to make people aware of them (perhaps in the wiki?). Applications like your bpsh and the simulator you've used in your videos can be the first ones listed.

@zebastian

Copy link
Copy Markdown
Contributor Author

sure @250MHz: i will make it a standalone project and let you know the reference here in the ticket once done.

@aggelis

aggelis commented Jun 24, 2026

Copy link
Copy Markdown

Hi @zebastian and all,

Wanted to share that we independently implemented a very similar concept, which proved invaluable during our DTN flight tests with ION on OPS-SAT (March 2023): https://nebula.esa.int/content/smallsat-dtn (see exec. summary & presentation).

We gave it a similar name: bpshell.

It also proved invaluable to our joint DTN flight demo with ESA's DTN team on OPS-SAT (January 2024), where we provided the onboard DTN implementation and ESA provided the ground implementation (dtna) (see section III OPS-SAT DTN Demonstrator): https://www.techrxiv.org/doi/full/10.36227/techrxiv.170831453.30339079/v1

In the above flight campaigns, this type of tool allowed us to leverage the full Linux ecosystem available to experimenters on the OPS-SAT SEPP OBC over the Bundle Protocol, without reinventing the wheel for remote payload operations, and it provided a natural foundation for automating onboard ION DTN operations.

In my opinion it would be beneficial to ION DTN advancement if this functionality finds its way either into ION upstream or as a well-maintained standalone project, especially as Linux-based OBCs are found more often in new missions.

Best regards,
Aggelis Aggelis

@zebastian

Copy link
Copy Markdown
Contributor Author

i now moved the bpsh, together with the mqtt CLA:

see subproject:
https://github.com/zebastian/ION-DTN-CONTRIB/tree/master/APP/bpsh
of:
https://github.com/zebastian/ION-DTN-CONTRIB

build tooling / testing is close to ION-DTN, given a linux machine with ION-DTN installed this should be installable with:

./install-deps.sh APP_BPSH
autoreconf -fi
./configure --enable-app-bpsh
make
sudo make install
./test.sh APP_BPSH

@aggelis thank you for your insights! Glad to see such a feature can be put to use in "the wild".
As suggested i now moved bpsh to a standalone project, which acts as an umbrella for other ION-DTN contributions as well:

Please let me know what you think.
Also please let me know, whether the bpshell did contain features currently missing in bpsh.

@aggelis

aggelis commented Jul 1, 2026

Copy link
Copy Markdown

@zebastian, i will try to set up and test bpsh locally and share my feedback.

@zebastian

Copy link
Copy Markdown
Contributor Author

@aggelis great, let me know if you into any trouble.
Note i pushed some changes for bpsh (subshell uid config, password auth).
Anyway no breaking changes to the previous state

@250MHz

250MHz commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

It's very good to see applications like this though, and we should have a list of references of community utilities to make people aware of them (perhaps in the wiki?). Applications like your bpsh and the simulator you've used in your videos can be the first ones listed.

FYI there's this wiki page now which you should be able to edit https://github.com/nasa-jpl/ION-DTN/wiki/Ecosystem

@zebastian

Copy link
Copy Markdown
Contributor Author

@250MHz i changed the wiki page and extended it to the other present APPs and CLAs in ION-DTN-CONTRIB.

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