docs/06-networking
Tuesday 10 March 2026

Peer-to-Peer Networking

This guide explains how Mau discovers peers and synchronizes data across the network using a combination of Kademlia DHT, mDNS, and direct HTTP/TLS connections.

Overview

Mau’s networking layer is designed to enable truly decentralized peer-to-peer communication:

  • Local Discovery: mDNS-SD finds peers on the same LAN automatically
  • Global Discovery: Kademlia DHT enables internet-wide peer lookup
  • Secure Transport: All connections use mutual TLS authentication with PGP-derived certificates
  • No Central Servers: Each node is equal; no privileged infrastructure required
┌─────────────┐                          ┌─────────────┐
│   Peer A    │◄────── Local Network ────►│   Peer B    │
│  (Laptop)   │         (mDNS-SD)         │  (Desktop)  │
└──────┬──────┘                          └──────┬──────┘
       │                                        │
       └────────────► Kademlia DHT ◄───────────┘
                      (Internet-wide)
                            │
                            ▼
                    ┌──────────────┐
                    │   Peer C     │
                    │  (Server)    │
                    └──────────────┘

Discovery Mechanisms

1. Local Network Discovery (mDNS-SD)

What it is:
Multicast DNS Service Discovery (mDNS-SD) allows peers to advertise themselves on the local area network and discover each other without any central coordination.

How it works:

  1. Each Mau instance announces itself via mDNS with service name _mau._tcp.local
  2. The announcement includes:
    • PGP fingerprint (unique identifier)
    • IP address and port
  3. Other peers on the same network listen for these announcements
  4. When a peer needs to contact another peer, it queries mDNS for that fingerprint

Use cases:

  • Home networks (laptop, phone, desktop syncing)
  • Office LANs (team collaboration without internet)
  • IoT devices on local networks
  • Development and testing

Code example:

1import "github.com/mau-network/mau"
2
3server, _ := mau.NewServer(account, ":8080")
4
5// mDNS is enabled by default
6// Peers on your LAN will automatically discover this instance

Service format:

<fingerprint>._mau._tcp.local.

Example: ABC123DEF456._mau._tcp.local. resolves to 192.168.1.100:8080


2. Internet Discovery (Kademlia DHT)

What it is:
Kademlia is a distributed hash table (DHT) protocol that enables decentralized peer lookup across the internet. It’s the same technology used by BitTorrent and IPFS.

Why Kademlia?

  • Decentralized: No central registry or DNS required
  • Efficient: O(log n) lookup complexity
  • Robust: Self-healing network topology
  • Scalable: Works with millions of peers

How Kademlia Works

Node IDs:
Each peer is identified by its PGP fingerprint (160 bits for RSA, 256 bits for Ed25519). The fingerprint serves as both the peer’s identity and its position in the DHT keyspace.

XOR Distance Metric:
Kademlia defines “closeness” using the XOR metric:

distance(A, B) = A ⊕ B  (bitwise XOR)

Peers that are “closer” (smaller XOR distance) are considered neighbors.

Routing Table (k-buckets):
Each peer maintains a routing table with 160 buckets (for RSA fingerprints), where:

  • Bucket i stores peers at XOR distance [2^i, 2^(i+1))
  • Each bucket holds up to k = 20 peers
  • Least-recently-seen (LRS) eviction policy

Peer Lookup:
To find a peer with fingerprint F:

  1. Query the α = 3 closest peers you know about F
  2. Each responds with their k = 20 closest peers to F
  3. Recursively query the newly discovered closest peers
  4. Continue until F is found or no closer peers are discovered

Routing Table Maintenance:

  • Ping: Verify peer liveness before evicting old contacts
  • Refresh: Every hour, refresh stale buckets by performing random lookups
  • Implicit updates: Add peers to routing table when they contact you

Kademlia Parameters

Constant Value Description
B 160 Number of buckets (160 bits for v4 keys)
K 20 Max peers per bucket (replication factor)
α 3 Parallel lookup requests
STALL_PERIOD 1 hour Bucket refresh interval
PING_MIN_BACKOFF 30 seconds Minimum time between pings to same peer

Joining the Network

To join the Kademlia network, you need at least one bootstrap peer:

 1import "github.com/mau-network/mau"
 2
 3bootstrap := []*mau.Peer{
 4    {
 5        Fingerprint: mau.MustParseFingerprintFromString("ABC123..."),
 6        Address:     "peer.example.com:8080",
 7    },
 8}
 9
10server, _ := mau.NewServer(account, ":8080", mau.WithBootstrapPeers(bootstrap))
11
12// Server will:
13// 1. Add bootstrap peer to routing table
14// 2. Query for itself to discover neighbors
15// 3. Refresh all buckets to populate routing table
16// 4. Start periodic refresh of stale buckets

What happens during join:

  1. Add bootstrap peers to routing table
  2. Perform FIND_PEER(self) to discover neighbors
  3. Refresh all buckets to populate routing table
  4. Start background refresh process for stale buckets

DHT API Endpoints

Mau exposes two Kademlia RPC endpoints:

1. Ping (GET /kad/ping)
Verify peer liveness.

Request:

GET https://<peer-address>/kad/ping

Response:

200 OK

2. Find Peer (GET /kad/find_peer/{fingerprint})
Find the k-closest peers to a given fingerprint.

Request:

GET https://<peer-address>/kad/find_peer/ABC123DEF456...

Response:

 1[
 2  {
 3    "fingerprint": "ABC123DEF456...",
 4    "address": "192.168.1.100:8080"
 5  },
 6  {
 7    "fingerprint": "789XYZ012...",
 8    "address": "peer.example.com:8080"
 9  }
10]

Fingerprint Resolvers

Mau uses a resolver pattern to support multiple discovery strategies:

1type FingerprintResolver func(
2    ctx context.Context,
3    fingerprint Fingerprint,
4    addresses chan<- string,
5) error

A resolver receives a fingerprint and sends discovered addresses to the addresses channel.

Built-in Resolvers

1. Static Address

Always returns the same address (useful for testing or known peers):

1resolver := mau.StaticAddress("peer.example.com:8080")

2. Local Friend Address (mDNS)

Discovers peers on the local network:

1resolver := mau.LocalFriendAddress

3. Internet Friend Address (Kademlia)

Uses the DHT to find peers on the internet:

1resolver := mau.InternetFriendAddress(server)

Using Resolvers

Resolvers are used internally by the Client when connecting to a peer:

1client, err := account.Client(
2    targetFingerprint,
3    []string{"fallback-address.com:8080"}, // fallback if resolvers fail
4)

The client will:

  1. Try all configured resolvers in parallel
  2. Use the first address that successfully connects
  3. Fall back to the provided addresses if all resolvers fail

Connection Lifecycle

Establishing a Connection

  1. Resolve Peer Address

    • Try mDNS (local network)
    • Try Kademlia DHT (internet)
    • Fall back to known addresses
  2. TLS Handshake

    • Client presents certificate derived from its PGP key
    • Server verifies client certificate fingerprint
    • Server presents certificate derived from its PGP key
    • Client verifies server certificate fingerprint
  3. Add to Routing Table

    • When a peer contacts you, add them to your Kademlia routing table
    • Move existing peers to the tail (LRU update)
    • If bucket is full, ping least-recently-seen peer
  4. Request/Response

    • Execute API call (ping, find_peer, sync, etc.)
    • Connection is encrypted and mutually authenticated

Handling Peer Failures

Detection:

  • Timeout on HTTP request
  • TLS handshake failure
  • Invalid certificate fingerprint

Recovery:

  • Remove peer from routing table
  • Try next closest peer (if doing lookup)
  • Backoff on repeated failures

Network Security

Mutual TLS Authentication

Every connection requires both peers to present valid certificates:

Client                        Server
  │                             │
  ├─── ClientHello ────────────►│
  │                             │
  │◄─── ServerCertificate ──────┤
  │     (PGP-derived cert)      │
  │                             │
  ├─── ClientCertificate ──────►│
  │     (PGP-derived cert)      │
  │                             │
  │◄─── Verify Fingerprint ─────┤
  │                             │
  ├─── Verify Fingerprint ─────►│
  │                             │
  │◄─── Encrypted Channel ──────┤

What this prevents:

  • Man-in-the-middle attacks
  • Impersonation
  • Eavesdropping
  • Unauthorized access

Privacy Considerations

IP Address Exposure:
Your IP address is visible to:

  • Peers you directly connect to
  • Bootstrap peers
  • Peers queried during DHT lookups

Metadata Leakage:
The DHT reveals:

  • Your fingerprint (public key hash)
  • Approximate network location (bucket structure)
  • Online/offline status

Mitigation:

  • Use Tor or VPN for IP anonymity
  • Run behind NAT/firewall
  • Limit bootstrap peers to trusted nodes
  • See Privacy & Security for more

Network Topology

Flat Peer-to-Peer

Mau is a flat network: all nodes are equal. There are no:

  • Supernodes
  • Master servers
  • Privileged infrastructure
  • Centralized registries

Every peer can:

  • Accept connections from others
  • Initiate connections to others
  • Route DHT queries
  • Store and serve data

Network Address Translation (NAT)

Problem:
Most home/office networks use NAT, which prevents incoming connections from the internet.

Solution (optional):
Mau includes UPnP support to automatically configure port forwarding:

1server, _ := mau.NewServer(account, ":8080", mau.WithUPnP())

If UPnP succeeds, the server becomes publicly reachable on the internet.

Fallback:

  • Peers behind NAT can still initiate outgoing connections
  • They can participate in the DHT (respond to queries from peers they contacted)
  • For full bidirectional reachability, manually configure port forwarding or use a relay

Practical Examples

Example 1: Local-Only Network (No Internet)

 1package main
 2
 3import "github.com/mau-network/mau"
 4
 5func main() {
 6    account, _ := mau.LoadAccount("./my-account")
 7    
 8    // Only mDNS, no DHT
 9    server, _ := mau.NewServer(account, ":8080")
10    
11    // Peers on your LAN will auto-discover this instance
12    server.Start()
13}

Example 2: Internet-Wide Network with Bootstrap

 1package main
 2
 3import "github.com/mau-network/mau"
 4
 5func main() {
 6    account, _ := mau.LoadAccount("./my-account")
 7    
 8    bootstrap := []*mau.Peer{
 9        {
10            Fingerprint: mau.MustParseFingerprintFromString("ABC123..."),
11            Address:     "bootstrap.mau-network.org:8080",
12        },
13    }
14    
15    server, _ := mau.NewServer(
16        account,
17        ":8080",
18        mau.WithBootstrapPeers(bootstrap),
19        mau.WithUPnP(), // optional: auto-configure port forwarding
20    )
21    
22    server.Start()
23}

Example 3: Client-Only (No Incoming Connections)

 1package main
 2
 3import "github.com/mau-network/mau"
 4
 5func main() {
 6    account, _ := mau.LoadAccount("./my-account")
 7    
 8    // Connect to a friend
 9    friendFingerprint := mau.MustParseFingerprintFromString("DEF789...")
10    client, _ := account.Client(
11        friendFingerprint,
12        []string{"friend.example.com:8080"},
13    )
14    
15    // Fetch friend's data
16    posts, _ := client.List("/posts")
17    // ...
18}

Debugging Network Issues

Check Routing Table

1# List all peers in routing table
2mau peers list
3
4# Show detailed bucket information
5mau peers buckets

Test Peer Discovery

1# Test mDNS discovery
2mau discover --local
3
4# Test Kademlia lookup
5mau discover --fingerprint ABC123DEF456...

Monitor Network Traffic

1# Enable verbose logging
2export MAU_LOG_LEVEL=debug
3mau server start
4
5# Logs will show:
6# - Peer connections
7# - DHT queries
8# - Routing table updates

Performance Tuning

Bootstrap Peer Selection

Use multiple bootstrap peers:

1bootstrap := []*mau.Peer{
2    {Fingerprint: ..., Address: "bootstrap1.example.com:8080"},
3    {Fingerprint: ..., Address: "bootstrap2.example.com:8080"},
4    {Fingerprint: ..., Address: "bootstrap3.example.com:8080"},
5}

Why:

  • Redundancy (if one is offline)
  • Faster routing table population
  • Geographic diversity (lower latency)

Parallelism Factor (α)

The α parameter controls how many peers are queried in parallel during lookup. Default is α = 3.

Higher α = faster lookups, more bandwidth.
Lower α = slower lookups, less bandwidth.

Bucket Refresh Interval

Buckets are refreshed every STALL_PERIOD = 1 hour by default. For more active networks:

1// Adjust in constants.go (requires rebuilding)
2const dht_STALL_PERIOD = 30 * time.Minute

Next Steps


Further Reading