PLC Directory over DNS (experiment)
Go 89.5%
Makefile 6.0%
Dockerfile 4.5%
4 1 0

Clone this repository

https://tangled.org/tree.fail/plcdns
git@tangled.org:tree.fail/plcdns

For self-hosted knots, clone URLs may differ based on your setup.

README.md

plcdns#

PLC Directory over DNS - A DNS server that resolves Bluesky/AT Protocol DID PLC identifiers to their associated metadata via DNS TXT records.

Features#

  • 🔍 Handle Resolution - Resolve DIDs to their AT Protocol handles
  • 🌐 PDS Discovery - Find Personal Data Server endpoints
  • 🔧 Service Resolution - Resolve any service from DID documents (PDS, labelers, etc.)
  • 🔑 Public Key Lookup - Retrieve verification public keys
  • Caching - 5-minute cache to reduce PLC directory load
  • 🐳 Docker Support - Easy deployment with Docker
  • Comprehensive Tests - Full test coverage

Installation#

From Source#

# Clone the repository
git clone https://tangled.org/@tree.fail/plcdns
cd plcdns

# Install dependencies
go mod download

# Build
go build -o plcdns

# Run
./plcdns -port 8053

Using Docker#

# Build
docker build -t plcdns .

# Run
docker run -p 8053:8053 -e DNS_PORT=8053 plcdns

Using Go Install#

go install tangled.org/@tree.fail/plcdns@latest

Usage#

Starting the Server#

# Default port (8053)
./plcdns

# Custom port via flag
./plcdns -port 9053

# Custom port via environment variable
DNS_PORT=9053 ./plcdns

# Custom PLC directory
./plcdns -port 8053 -plc https://plc.directory

Query Formats#

The server supports four types of queries using different subdomain prefixes:

Query Type Format Returns Example
Handle _handle.<did>.plc.atscan.net AT Protocol handle test.bsky.social
PDS _pds.<did>.plc.atscan.net PDS endpoint URL https://bsky.social
Labeler _labeler.<did>.plc.atscan.net Labeler service URL https://mod.bsky.app
Public Key _pubkey.<did>.plc.atscan.net Public key (multibase) zQ3sh...

Query Examples#

# Using dig
dig @localhost -p 8053 _handle.z72i7hdynmk6r22z27h6tvur.plc.atscan.net TXT
dig @localhost -p 8053 _pds.z72i7hdynmk6r22z27h6tvur.plc.atscan.net TXT
dig @localhost -p 8053 _labeler.ar7c4by46qjdydhdevvrndac.plc.atscan.net TXT
dig @localhost -p 8053 _pubkey.z72i7hdynmk6r22z27h6tvur.plc.atscan.net TXT

# Using nslookup
nslookup -type=TXT _handle.z72i7hdynmk6r22z27h6tvur.plc.atscan.net localhost -port=8053

# Using host
host -t TXT _pds.z72i7hdynmk6r22z27h6tvur.plc.atscan.net localhost -p 8053

Example Response#

$ dig @localhost -p 8053 _handle.z72i7hdynmk6r22z27h6tvur.plc.atscan.net TXT +short
"bsky.app"

$ dig @localhost -p 8053 _pds.z72i7hdynmk6r22z27h6tvur.plc.atscan.net TXT +short
"https://bsky.social"

Configuration#

Command Line Flags#

Flag Default Description
-port 8053 DNS server port
-plc https://plc.directory PLC directory URL

Environment Variables#

Variable Description
DNS_PORT Override default port (8053)

Cache Settings#

  • TTL: 300 seconds (5 minutes) for DNS records
  • Cache Duration: 5 minutes for DID documents
  • Cache Type: In-memory map

Testing#

# Run all tests
go test -v

# Run with coverage
go test -v -cover

# Generate coverage report
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

# Run with race detector
go test -v -race

API Reference#

DID Document Structure#

The server fetches DID documents from the PLC directory with the following structure:

{
  "@context": ["https://www.w3.org/ns/did/v1"],
  "id": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "alsoKnownAs": ["at://bsky.app"],
  "verificationMethod": [{
    "id": "did:plc:z72i7hdynmk6r22z27h6tvur#atproto",
    "type": "Multikey",
    "controller": "did:plc:z72i7hdynmk6r22z27h6tvur",
    "publicKeyMultibase": "zQ3sh..."
  }],
  "service": [{
    "id": "#atproto_pds",
    "type": "AtprotoPersonalDataServer",
    "serviceEndpoint": "https://bsky.social"
  }]
}

Deployment#

Systemd Service#

Create /etc/systemd/system/plcdns.service:

[Unit]
Description=PLC Directory DNS Server
After=network.target

[Service]
Type=simple
User=plcdns
ExecStart=/usr/local/bin/plcdns -port 53
Restart=always
Environment="DNS_PORT=53"

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable plcdns
sudo systemctl start plcdns

Docker Compose#

version: '3.8'

services:
  plcdns:
    build: .
    ports:
      - "53:53/udp"
      - "53:53/tcp"
    environment:
      - DNS_PORT=53
    restart: unless-stopped

Running on Port 53#

To run on the standard DNS port (53), you need elevated privileges:

Linux (with capabilities):

sudo setcap 'cap_net_bind_service=+ep' ./plcdns
./plcdns -port 53

Using sudo:

sudo ./plcdns -port 53

Architecture#

┌─────────────┐
│ DNS Client  │
└──────┬──────┘
       │ Query: _handle.<did>.plc.atscan.net
       ▼
┌─────────────────┐
│   DNS Server    │
│  (Port 8053)    │
└────────┬────────┘
         │
         ├─── Cache Check (5 min TTL)
         │
         ▼
┌─────────────────┐
│ PLC Directory   │
│ (plc.directory) │
└─────────────────┘
         │
         ▼ DID Document
┌─────────────────┐
│  Parse & Return │
│   TXT Record    │
└─────────────────┘

Performance#

  • Cache Hit: ~1ms response time
  • Cache Miss: ~50-200ms (depends on PLC directory)
  • Concurrent Requests: Supports thousands of concurrent queries
  • Memory Usage: ~10-50MB depending on cache size

Contributing#

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Code Style#

  • Follow standard Go conventions
  • Run go fmt before committing
  • Add tests for new features
  • Update documentation as needed

License#

MIT License - see LICENSE file for details

Acknowledgments#

  • Built with miekg/dns - DNS library for Go
  • Inspired by the AT Protocol ecosystem

Roadmap#

  • DNSSEC support
  • Prometheus metrics endpoint
  • Redis cache backend option
  • Rate limiting
  • Multiple PLC directory fallbacks
  • Web UI for testing queries
  • REST API endpoint

Made with ❤️ for the AT Protocol community