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.
Mau’s networking layer is designed to enable truly decentralized peer-to-peer communication:
┌─────────────┐ ┌─────────────┐
│ Peer A │◄────── Local Network ────►│ Peer B │
│ (Laptop) │ (mDNS-SD) │ (Desktop) │
└──────┬──────┘ └──────┬──────┘
│ │
└────────────► Kademlia DHT ◄───────────┘
(Internet-wide)
│
▼
┌──────────────┐
│ Peer C │
│ (Server) │
└──────────────┘
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:
_mau._tcp.localUse cases:
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
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?
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:
i stores peers at XOR distance [2^i, 2^(i+1))Peer Lookup:
To find a peer with fingerprint F:
FFF is found or no closer peers are discoveredRouting Table Maintenance:
| 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 |
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:
FIND_PEER(self) to discover neighborsMau 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]
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.
Always returns the same address (useful for testing or known peers):
1resolver := mau.StaticAddress("peer.example.com:8080")
Discovers peers on the local network:
1resolver := mau.LocalFriendAddress
Uses the DHT to find peers on the internet:
1resolver := mau.InternetFriendAddress(server)
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:
Resolve Peer Address
TLS Handshake
Add to Routing Table
Request/Response
Detection:
Recovery:
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:
IP Address Exposure:
Your IP address is visible to:
Metadata Leakage:
The DHT reveals:
Mitigation:
Mau is a flat network: all nodes are equal. There are no:
Every peer can:
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:
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}
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}
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}
1# List all peers in routing table
2mau peers list
3
4# Show detailed bucket information
5mau peers buckets
1# Test mDNS discovery
2mau discover --local
3
4# Test Kademlia lookup
5mau discover --fingerprint ABC123DEF456...
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
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:
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.
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