Skip to main content

A federated end-to-end encrypted messaging protocol delivered over DNS

Project description

DNS Mesh Protocol

End-to-end encrypted messaging with no central server, no app store, no gatekeeper — delivered over DNS, on the relays and infrastructure the internet already runs on.


What this is

DMP is an open, free messaging protocol built around one job: deliver a private message from one person to another, even when the network actively tries to stop you. Not a company, not a product — a specification anyone can implement, plus a permissively-licensed reference implementation.

Think of it as the email of private messaging: nobody owns email, nobody can shut it off. DMP aims for the same durability, with end-to-end encryption baked in from the start.

Why it exists

Today's "private" messaging looks like this:

You ↔ One company ↔ The other person

That company is a single point of failure. They can be subpoenaed, blocked at a national border, acquired, hacked, or simply shut down. End-to-end encryption protects message content — none of it protects you from the company in the middle disappearing or being made to.

DMP removes the company from the middle.

Who this is for

  • Journalists and sources in countries where the usual apps are blocked, monitored, or unsafe to have on a phone.
  • Organizations that want a backup channel that keeps working when their primary vendor is down, regulated, or subpoenaed.
  • Privacy advocates who don't want a phone number, an account, or any company in between two people who agreed to talk.
  • Builders who need a small, auditable messaging primitive they can embed into their own products with no licensing or vendor lock-in.

If DNS works on your network, DMP works on your network. That's the whole pitch.

→ 5-minute training deck · → Try the public node · → Self-host


Status: alpha, pre-external-audit. Full federation (client fan-out + union reader + node-side anti-entropy + manifest gossip), bootstrap discovery, key rotation + revocation (M5.4), multi-tenant node auth with per-user publish tokens (M5.5), cross-zone receive

  • first-message claim layer (M8 / 0.4.x), and the formal protocol spec are shipped. The remaining path to v1.0 is a certification backlog: external cryptographic audit, mobile/web clients, standalone CLI binaries, traffic-analysis hardening.

Don't route secrets through DMP until the external cryptographic audit is done. The codebase has had ~40+ rounds of automated review across all milestones, but automated review is not a substitute for professional cryptanalysis. A human auditor catches a different class of bugs (crypto composition errors, side-channel weaknesses, protocol-level attacks, implementation-vs-spec drift) that no amount of LLM-driven pattern matching will find. The audit is a post-beta deliverable; until then treat DMP as experimental for confidentiality-critical traffic.

How it works (30-second version)

Instead of sending messages through a central server you have to trust, DNS Mesh Protocol encrypts each message end-to-end and writes it as DNS records on a node you choose. The recipient looks those records up the same way any computer looks up google.com. If DNS works on your network, DMP works on your network.

  • One docker container is a complete, deployable node.
  • One command-line tool covers identity, key management, send, and receive.
  • One protocol composes Ed25519 signatures, X25519 ECDH, ChaCha20-Poly1305, Argon2id passphrase derivation, one-time prekeys for forward secrecy, and Reed-Solomon erasure coding for chunk loss.

Try it on dnsmesh.io

The public reference node at dnsmesh.io is open: no operator approval, no email, just a CLI command. It advertises open registration, the M8 claim-provider role, and acts as the canonical bootstrap seed for the federated network.

pipx install dnsmesh
dnsmesh init alice --domain <your-zone> --endpoint dnsmesh.io
dnsmesh tsig register --node dnsmesh.io     # one HTTPS hop, mints a TSIG key
dnsmesh identity publish                     # DNS UPDATE + TSIG, no HTTPS

After M9 the protocol is DNS both directions. The only HTTPS exchange is the one-time tsig register step that gets you a TSIG key bound to your zone. Every subsequent record write — identity, prekeys, mailbox slots, chunks, claim publishes — goes over RFC 2136 DNS UPDATE signed under that key. Reads are plain DNS TXT queries.

Curious what a node is currently publishing: dig @dnsmesh.io _dnsmesh-heartbeat.dnsmesh.io TXT +short.

Self-host

git clone https://github.com/oscarvalenzuelab/DNSMeshProtocol.git
cd DNSMeshProtocol

# Install the CLI
pip install -e .

# Build and run the node
docker build -t dnsmesh-node:latest .
docker run -d -p 5353:5353/udp -p 8053:8053/tcp \
  -v dnsmesh-data:/var/lib/dmp dnsmesh-node:latest

# Set up an identity and send your first message
export DMP_PASSPHRASE=a-strong-passphrase
dnsmesh init alice --domain mesh.local \
               --endpoint http://127.0.0.1:8053 \
               --dns-host 127.0.0.1 --dns-port 5353
dnsmesh identity publish

For a production Ubuntu deployment with auto-TLS and systemd:

curl -fsSL https://raw.githubusercontent.com/oscarvalenzuelab/DNSMeshProtocol/main/deploy/native-ubuntu/install.sh \
    | sudo DMP_NODE_HOSTNAME=dmp.example.com bash

Then delegate a DNS subdomain to the node so clients on the public internet can resolve records served by the node. Without this step, dig @1.1.1.1 id-XXX.example.com TXT returns nothing even though the node has the record — the registrar's nameservers don't know about DMP records. The fix is one NS record at the registrar plus one env var on the node:

At the registrar (DigitalOcean / Cloudflare / etc.):
  mesh.example.com.   IN NS   example.com.

On the node:
  DMP_DOMAIN=mesh.example.com
  sudo systemctl restart dnsmesh-node

Full walk-through with screenshots and verification steps: Deployment → DNS subdomain delegation.

Once your node is up, point the CLI at it via the public hostname:

dnsmesh init alice@dmp.example.com                # auto-splits user + zone
dnsmesh tsig register --node dmp.example.com      # one-shot HTTPS, mints TSIG key
dnsmesh identity publish                          # DNS UPDATE under that key

The tsig register flow walks the same Ed25519 challenge/confirm ceremony the legacy bearer-token path uses, but it returns a TSIG key (RFC 8945 HMAC-SHA256) that's persisted in your config and auto-attached to every subsequent record publish. The CLI rebuilds its writer accordingly: _DnsUpdateWriter → port 53 → your home node. After register, no HTTPS leaves your machine for the protocol's normal flow.

Full walk-through with two users: Getting Started.

What you get

  • End-to-end encryption with forward secrecy for prekey-consumed messages. Past messages stay safe if long-term keys leak later; see Forward secrecy and prekeys.
  • Signed sender authentication. With pinned contacts, unknown signers are dropped. Without, receive runs in trust-on-first-use.
  • Zone-anchored identity addresses. alice@alice.example.com. Squatting requires compromising DNS for the zone.
  • Cross-chunk erasure coding. Loss of up to n-k chunks still reconstructs the message.
  • Resolver resilience. ResolverPool fans queries across multiple upstream resolvers with oracle-based demotion on lying resolvers. dnsmesh resolvers discover auto-builds the pool from public resolvers.
  • Multi-node federation (client AND node side). FanoutWriter publishes to every cluster node (quorum = ceil(N/2)); UnionReader reads the union with dedup. Nodes run pull-based anti-entropy against their peers so a node that was offline catches up when it rejoins. A 3-node docker-compose.cluster.yml is a checked-in operator starting point; see Clustered deployment.
  • Key rotation + revocation. dnsmesh identity rotate --experimental publishes a co-signed RotationRecord (new key ← old key) plus an optional self-signed RevocationRecord when --reason compromise or --reason lost_key is set. Rotation-aware contacts chain-walk from their pinned key to the current head automatically; a revocation aborts trust on any path that touches the revoked key. See docs/protocol/rotation.md.
  • DNS-only federation (M9). Inter-node coordination — heartbeat publishes at _dnsmesh-heartbeat.<zone>, transitive discovery via _dnsmesh-seen.<zone>, claim-provider discovery, even cross-zone first-contact claim publishes — all goes over plain DNS, no HTTP between independent nodes. The legacy /v1/heartbeat, /v1/nodes/seen, /v1/claim/publish, and /v1/info HTTP routes are gone. Cluster anti-entropy (/v1/sync/digest + /v1/sync/pull) is the documented HA-only exception inside one operator's trust domain — see the boundary doc.
  • DNS UPDATE + TSIG writes (M9). DMP_DNS_UPDATE_ENABLED=1 switches publish from HTTP /v1/records/* to RFC 2136 UPDATE signed with RFC 8945 TSIG. Per-user keys minted via POST /v1/registration/tsig-confirm (the one-shot HTTPS registration step) carry a per-user pattern scope — slot-*.mb-*.<zone>, chunk-*-*.<zone>, id-<sha256(subject)>.<zone> — so registered users can't overwrite each other's records on shared zones. Operator caps (max_ttl, max_value_bytes, max_values_per_name) apply to UPDATE writes the same way they do to HTTP publishes.
  • Anti-takeover registration. mint_for_subject does the existence check + key insert atomically under one lock, so two concurrent tsig-confirm calls for the same subject under different signing keys can't both succeed.
  • Zero-config onboarding via bootstrap discovery. Given just alice@example.com, dnsmesh bootstrap discover me@my-domain --auto-pin resolves the cluster, verifies the two-hop trust chain (bootstrap signer → cluster operator), and cuts over atomically.
  • Persistent, size-bounded node. sqlite storage, TTL cleanup, token-bucket rate limits, bounded concurrency, Prometheus metrics.
  • Docker-first deploy. docker compose for dev, Caddy + Let's Encrypt overlay for production.
  • Formal protocol spec. Wire format, routing, flows, and threat model at docs/protocol/. Every constant cross-verified against the source; designed so a third party can build an interoperable client.

Running a node

# Dev
docker compose up -d

# Production (real hostname, auto TLS)
export DMP_NODE_HOSTNAME=dmp.example.com
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

See Deployment. Before shipping to production, read the operator hardening guide, a mandatory checklist covering TLS, token hygiene, operator signing-key handling, DNS zone hygiene, file permissions, and network exposure.

Project layout

dmp/
├── core/       Protocol primitives: crypto, chunking, erasure,
│               manifests, identity, prekeys, DNS encoding
├── network/    DNSRecordWriter / DNSRecordReader abstraction +
│               Cloudflare, Route53, BIND, in-memory backends
├── storage/    SqliteMailboxStore: persistent TTL-aware record store
├── server/     DMPNode: UDP DNS server, HTTP API, cleanup worker,
│               metrics, rate limiting, structured logging
├── client/     DMPClient: send / receive / identity / prekeys
└── cli.py      `dnsmesh` command-line interface

docs/          Jekyll docs site (Just the Docs theme, GitHub Pages)
               Includes docs/protocol/ for the formal wire spec
tests/         1050+ unit, integration, fuzz, and docker-in-the-loop tests
               Includes tests/fuzz/ (hypothesis property tests) and
               tests/test_vectors.py (golden interop test vectors).
Dockerfile, docker-compose.yml, docker-compose.prod.yml, Caddyfile

Tests

pip install -e ".[dev]"
pytest                                         # ~1050 tests (incl. fuzz)
docker build -t dnsmesh-node:latest .
pytest tests/test_docker_integration.py        # 6 docker tests (incl. M5.4 rotation)
pytest tests/test_compose_cluster.py           # 3 compose-cluster tests
python examples/docker_e2e_demo.py             # single-node send/receive + rotation demo
python examples/cluster_e2e_demo.py            # 3-node federated e2e demo

Production installs use the hashed lockfile:

pip install --require-hashes -r requirements.lock
pip install . --no-deps

Not a good fit for

  • Real-time chat (seconds-to-minutes latency by design)
  • File transfer or media payloads
  • Anonymity from traffic analysis (DMP hides content, not metadata)

Contributing

See CONTRIBUTING.md. Every PR that changes behavior needs a test. Security-sensitive changes in dmp/core/crypto.py, dmp/core/manifest.py, dmp/core/prekeys.py, or the AEAD AAD surface get an extra round of review.

License

AGPL-3.0. If you host DMP as a service you must publish your source changes.

Author

Oscar Valenzuela · AlkamoD

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dnsmesh-0.5.2.tar.gz (520.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dnsmesh-0.5.2-py3-none-any.whl (351.8 kB view details)

Uploaded Python 3

File details

Details for the file dnsmesh-0.5.2.tar.gz.

File metadata

  • Download URL: dnsmesh-0.5.2.tar.gz
  • Upload date:
  • Size: 520.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dnsmesh-0.5.2.tar.gz
Algorithm Hash digest
SHA256 09481985678f1cd33009885bae8fe24d028e466d2f7addd06ccea467533830ac
MD5 94abe7e4dd92e32249ba7d39e39b3df5
BLAKE2b-256 75cdc1b86ec9c9b3448dda1455426f639c80b40978e718438960ec3d5b9fe205

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnsmesh-0.5.2.tar.gz:

Publisher: publish-pypi.yml on oscarvalenzuelab/DNSMeshProtocol

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dnsmesh-0.5.2-py3-none-any.whl.

File metadata

  • Download URL: dnsmesh-0.5.2-py3-none-any.whl
  • Upload date:
  • Size: 351.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dnsmesh-0.5.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8252285daaa4409743399e475a64c2f4a52030ae6a8edcb748164625cbe1e652
MD5 f8b0f6e7d47b460869ac368a2bcf157e
BLAKE2b-256 3d0b52a9dcad7bc594c8975641c715d4b25271257150ffc2d5c1ae61ef399dd4

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnsmesh-0.5.2-py3-none-any.whl:

Publisher: publish-pypi.yml on oscarvalenzuelab/DNSMeshProtocol

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page