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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Code Style#
- Follow standard Go conventions
- Run
go fmtbefore committing - Add tests for new features
- Update documentation as needed
License#
MIT License - see LICENSE file for details
Related Projects#
- AT Protocol - Authenticated Transfer Protocol
- Bluesky - Social network built on AT Protocol
- PLC Directory - DID PLC registry
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