Necropolis v1 release

This commit is contained in:
2026-07-07 04:37:29 +01:00
commit cf9c51a3df
69 changed files with 15056 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
# Mesh Routing
## How It Works
Every Necropolis node (operator and implants) runs a circuit relay v2 service via
`libp2p.EnableRelayService()`. This means any node can relay encrypted traffic between
any two peers on the network. There is no distinction between "relay nodes" and
"client nodes". Every node is both.
Routing works in layers:
1. **Direct stream**. The implant opens a persistent `/bc/1.0.0` stream to the operator
via a direct libp2p connection. This is the primary path.
2. **PubSub fallback**. If the direct stream is unavailable, beacons and results are
published to GossipSub topics. Every node relays these topics (`Topic.Relay()`),
so even offline implants receive messages when they reconnect (GossipSub keeps the
last 10 heartbeats of history).
3. **DHT dead-drop**. When both direct streams and PubSub are unavailable, implants poll
the DHT dead-drop at `/necropolis/cmd/<id>/<nonce>` every 30s. The operator publishes
signed command envelopes to the DHT, and implants fetch them in nonce sequence.
4. **Implant-to-implant relay**. When an implant cannot reach the operator directly or
through pubsub, it routes through another implant. This happens automatically:
- Every implant advertises as a relay in the DHT under `necropolis/relay/<op-peerid>`
- Every implant discovers other relay-capable implants via DHT and connects to them
- When the direct beacon stream to the operator fails, the implant tries a circuit
relay address through each connected relay peer (`/p2p/<relay>/p2p-circuit/p2p/<op>`)
- libp2p handles the circuit negotiation; the implant just connects and opens a stream
Every node also auto-discovers relay candidates from its connected peers. Once an
implant connects to another implant (or the operator connects to an implant), libp2p's
`EnableAutoRelay` finds it as a relay candidate automatically.
## Discovery Flow
```
Operator: advertises under necropolis/<peerid> (rendezvous)
advertises under necropolis/relay/<peerid> (relay service)
finds relay peers via DHT relay namespace
Implant: finds operator via DHT rendezvous
advertises as relay under necropolis/relay/<peerid>
discovers other implants in relay namespace
connects to discovered relay peers
when direct operator connection fails, routes through relay peers
```
## Relay Health
Implants track every discovered relay peer with its connection state, last-seen time,
and fail count. Relay candidates are sorted by failCount ascending, so reliable relays are tried first. Every 30 seconds the discovery loop prunes disconnected peers. If a
relay peer drops, the next beacon attempt skips it and tries the next candidate.
## CLI
The `list` command shows which implants are acting as relays:
```
necropolis> list
0: user@hostname [linux/amd64] last=5s peer=12D3Koo... [relay]
1: admin@server [linux/amd64] last=2s peer=12D3Koo...
2 connected (1 relays)
```
## Implementation
- `pkg/transport/node.go`: `RelayRendezvousString()`, `AdvertiseRelay()`,
`FindRelayPeers()`; `EnableRelayService` in node config; `EnableAutoRelay` with
dynamic relay candidate discovery
- `implant/core/agent.go`: `advertiseRelayLoop()`, `discoverRelaysLoop()`,
`relayRendezvous()`, relay routing in `getBeaconStream()`
- `server/core/operator.go`: `advertiseRelayLoop()`, `discoverRelayPeersLoop()`,
`IsRelay` flag on `ImplantRecord`