Initial commit: DDNS service with NIC V2 protocol support

Features:
- Token-based subdomain claiming
- NIC V2 (DynDNS2) protocol implementation
- Technitium DNS integration
- Rate limiting (10 req/min IP, 1 req/min token)
- Web UI for space claiming
- Docker/Docker Compose support
- Compatible with UniFi, pfSense, EdgeRouter

Module: git.dws.rip/DWS/dyn
This commit is contained in:
2026-02-01 16:37:09 -05:00
commit 2470f121e2
16 changed files with 1835 additions and 0 deletions

203
README.md Normal file
View File

@@ -0,0 +1,203 @@
# DWS Dynamic DNS Service
A self-hosted Dynamic DNS (DDNS) provider built with Go, featuring a minimalist web UI and NIC V2 (DynDNS2) protocol support for router compatibility.
## Overview
This service allows users to claim subdomains under `space.dws.rip` and update them via the standard DynDNS2 protocol, compatible with UniFi, pfSense, EdgeRouters, and other consumer routers.
## Architecture
```
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Router │ ──────► │ DWS Bridge │ ──────► │ Technitium DNS │
│ (DynDNS2) │ HTTP │ (Go) │ API │ (Authoritative)│
└─────────────┘ └──────────────┘ └─────────────────┘
┌──────────────┐
│ SQLite │
│ (token→sub) │
└──────────────┘
```
## Features
- **Token-based authentication** - No user accounts, just secure tokens
- **NIC V2 Protocol** - Compatible with most routers
- **Automatic wildcard records** - `*.your-space.space.dws.rip` also works
- **Rate limiting** - 10 req/min per IP, 1 req/min per token
- **Clean web UI** - Simple space claiming interface
- **Router config examples** - UniFi, pfSense, cURL snippets
## Quick Start
### Prerequisites
- Docker and Docker Compose
- Running Technitium DNS server with API access
- DNS delegation for `space.dws.rip` pointing to Technitium
### DNS Setup
In your parent `dws.rip` zone:
```text
space.dws.rip. IN NS dns.dws.rip.
dns.dws.rip. IN A <TECHNITIUM_IP>
```
### Configuration
Create a `.env` file:
```bash
# Required: Technitium DNS API endpoint
TECHNITIUM_URL=https://dns.dws.rip
# Authentication (choose one method)
# Method 1: API Token (recommended)
TECHNITIUM_TOKEN=your-api-token-here
# Method 2: Username/Password
TECHNITIUM_USERNAME=admin
TECHNITIUM_PASSWORD=your-password
# Optional: Domain configuration
BASE_DOMAIN=dws.rip
SPACE_SUBDOMAIN=space
# Optional: Rate limiting
RATE_LIMIT_PER_IP=10
RATE_LIMIT_PER_TOKEN=1
# Optional: Trusted proxies (comma-separated)
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12
```
### Deployment
```bash
# Clone and start
git clone <repo>
cd dyn
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down
```
## API Endpoints
### Web UI
- `GET /` - Claim space interface
### API
- `GET /api/check?subdomain=<name>` - Check subdomain availability
- `POST /api/claim` - Claim a subdomain (returns token once)
### DynDNS2 Protocol
- `GET /api/nic/update?hostname=<fqdn>&myip=<ip>`
- Auth: Basic auth with username `none` and password = token
- Returns: `good <ip>`, `nochg <ip>`, `badauth`, `nohost`, `911`
## Router Configuration
### UniFi / UDM
```
Service: dyndns
Hostname: your-space.space.dws.rip
Username: none
Password: <your-token>
Server: dyn.dws.rip/api/nic/update
```
### pfSense / OPNsense
```
Service Type: Custom
Update URL: https://dyn.dws.rip/api/nic/update?hostname=%h&myip=%i
Username: none
Password: <your-token>
```
### EdgeRouter
```bash
set service dns dynamic interface eth0 service custom-dyn host-name your-space.space.dws.rip
set service dns dynamic interface eth0 service custom-dyn login none
set service dns dynamic interface eth0 service custom-dyn password <your-token>
set service dns dynamic interface eth0 service custom-dyn protocol dyndns2
set service dns dynamic interface eth0 service custom-dyn server dyn.dws.rip
```
### Manual (cURL)
```bash
curl -u "none:<your-token>" \
"https://dyn.dws.rip/api/nic/update?hostname=your-space.space.dws.rip&myip=1.2.3.4"
```
## Database Schema
```sql
CREATE TABLE spaces (
token TEXT PRIMARY KEY,
subdomain TEXT UNIQUE NOT NULL,
last_ip TEXT,
updated_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
## Security
- **32-byte random tokens** - Generated with `crypto/rand`
- **Token displayed once** - No recovery mechanism (claim new space if lost)
- **Rate limiting** - Prevents brute force and abuse
- **No PII stored** - Just token-to-subdomain mapping
- **Input validation** - Subdomains validated (alphanumeric + hyphen only)
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `TECHNITIUM_URL` | Yes | - | Technitium API URL |
| `TECHNITIUM_TOKEN` | No* | - | API token for auth |
| `TECHNITIUM_USERNAME` | No* | - | Basic auth username |
| `TECHNITIUM_PASSWORD` | No* | - | Basic auth password |
| `BASE_DOMAIN` | No | `dws.rip` | Root domain |
| `SPACE_SUBDOMAIN` | No | `space` | Subdomain for user spaces |
| `DATABASE_PATH` | No | `./dyn.db` | SQLite database path |
| `SERVER_PORT` | No | `8080` | HTTP server port |
| `RATE_LIMIT_PER_IP` | No | `10` | Requests per minute per IP |
| `RATE_LIMIT_PER_TOKEN` | No | `1` | Updates per minute per token |
| `TRUSTED_PROXIES` | No | - | Comma-separated CIDRs |
*Either `TECHNITIUM_TOKEN` OR both `TECHNITIUM_USERNAME` and `TECHNITIUM_PASSWORD` required.
## Development
```bash
# Local development
go mod tidy
go run cmd/server/main.go
# Build
go build -o server cmd/server/main.go
# Test
go test ./...
```
## License
MIT
## Credits
Inspired by [benjaminbear/docker-ddns-server](https://github.com/benjaminbear/docker-ddns-server)