Start with a probe that could never succeed.
The custody relay checks whether its Earth peer is reachable by opening a TCP connection to it. The original timeout was three seconds. A TCP connect delivers nothing until the handshake completes, and the handshake needs one full round trip. Light needs 1.28 seconds to cover the Earth-Moon gap one way, and from the relay, through its actual path, the measured round trip was 3.4 seconds. The probe timed out before the SYN-ACK could physically arrive. Not sometimes. Every time, by the geometry of the solar system.
There is no bug in that tool; its author never imagined a wire 400,000 kilometers long. The observation generalizes: every timeout constant in every network tool encodes an assumption about the size of the world it was written on, and nearly all of them were written on a planet whose far side is 200 milliseconds away.
The kernel is not a custodian
The first relay composition made a subtler mistake, the one this post exists for. The contact plan was always-on: the relay held a permanent TCP contact toward each peer. Then the geometry closed. Luna occluded the path, the connection died on the far end, and the relay kept writing bundles into the socket. The writes succeeded. The daemon reported delivery. The bytes sat in the kernel's retransmit queue, backing off exponentially, owned by no one, and if that connection or that process went away, the queue went with it.
Every terrestrial instinct says this is fine. A full socket buffer drains in milliseconds. A dead peer comes back before the application notices. The kernel's retransmit machinery is so reliable at terrestrial scale that everyone has forgotten it is a best-effort courtesy, not a contract. An occlusion measured in hours breaks the courtesy. The relay claimed success because the socket accepted the write, and a socket accepting a write is a statement about local memory, not about Luna.
The Delay-Tolerant Networking architecture (RFC 4838) starts from the admission that end-to-end connectivity may not exist at any given moment. Its unit is the bundle, a self-contained message that can wait, and bundles move over convergence layer adapters, thin shims that carry the bundle layer across whatever exists on each hop: a TCP connection, a radio link, a database.
The current protocol is Bundle Protocol version 7 (RFC 9171). The lab runs uD3TN, D3TN's open implementation, as an ordinary containerized workload.
Topology by calendar
Terrestrial routing discovers its topology. Hello packets go out, adjacencies form, timers expire, and the protocol infers the graph because the graph is not knowable any other way. Nobody can predict when a backhoe finds the fiber.
Orbital topology is different in kind. A link in the sky exists because two antennas can see each other, and where every bird will be is computable years ahead. The graph is not discovered. It is scheduled. A contact plan writes the schedule down: this node reaches that node from this time to that time. Routing stops being inference about an unknowable present and becomes planning against a known future. post 009 (Forwarding Ahead of the Geometry) made that argument for forwarding state. The bundle layer makes it for delivery itself.
The network takes the blame
Here is the recomposed relay's contact manager. No protocol extension, no kernel patch. A loop in a container, running contact management as ordinary workload behavior. The whole composition ships as catalog/nodalarc/profiles/ud3tn-custody-relay.yaml:
env:
- name: DTN_EARTH
value_from: {tag: dtn_earth, interface: terr0, family: ipv4}
command:
- |-
manage() {
if timeout 15 bash -c "echo -n >/dev/tcp/$ip/4224"; then
# peer answers: direct mtcp contact, recall held bundles
aap2-config --schedule ... "$eid" "mtcp:$ip:4224"
aap2-storage-agent --storage-agent-eid dtn://relay.dtn/sqlite \
push --dest-eid-glob "$eid*"
else
# peer occluded: swap the contact to storage
aap2-config --schedule ... "$eid" "sqlite:"
fi
}
While the peer answers, bundles flow over a direct contact. The moment it stops answering, the contact is swapped to the daemon's sqlite storage adapter, and bundles for that peer land in a database instead of a dead socket. When geometry returns, the direct contact is restored and the held bundles are recalled and pushed. Notice the address of the storage: dtn://relay.dtn/sqlite. The queue is not a buffer hanging off an interface. It is a named hop in the network with an endpoint identity of its own, a place a message can legitimately be while it is on the way.
That is custody. Connectivity is an instantaneous fact, and at cislunar scale it is an instantaneous fact about the past. Custody is responsibility over time. The IP layer treats disconnection as a failure to route around right now. The bundle layer treats it as a schedule to wait out, and it moves the retransmit buffer out of a kernel that never agreed to the job and into a network element that did.
The recomposed session, earth-luna-dtn-custody, flies the same sky as the endpoint labs: 86 nodes, 357 candidate links, QUIC endpoints sharing the geometry. It ran the occlusion end to end. Bundles held in storage while the path was dark, recalled when it opened, delivered at light time. The relay never once claimed a delivery it had not made.
Everything in this post leaned on one quiet assumption: that the emulator knows where everything is. post 013 (Which Map, Which Clock) is about the day that assumption turned out to have a 12,709 kilometer hole in it.