Files
Necropolis-C2/docs/mesh-routing.md
T
2026-07-07 04:50:23 +01:00

3.4 KiB

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