Necropolis v1 release
This commit is contained in:
@@ -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`
|
||||
Reference in New Issue
Block a user