Files
dyn/README.md

8.3 KiB

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:

space.dws.rip.    IN NS    dns.dws.rip.
dns.dws.rip.      IN A     <TECHNITIUM_IP>

Configuration

Create a .env file:

# 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

# Clone and start
git clone https://git.dws.rip/DWS/dyn.git
cd dyn
docker-compose up -d

# View logs
docker-compose logs -f

# Stop
docker-compose down

Kubernetes / K3s

For production deployments on Kubernetes or K3s clusters:

# Clone repository
git clone https://git.dws.rip/DWS/dyn.git
cd dyn

# Create your secrets file
cp k8s/overlays/production/secrets.example.yaml k8s/overlays/production/secrets.yaml
# Edit secrets.yaml with your actual Technitium credentials

# Deploy with kustomize
kubectl apply -k k8s/overlays/production

# Or with kubectl 1.14+ (built-in kustomize)
kubectl apply -k k8s/overlays/production

# Check deployment status
kubectl get pods -n dyn-ddns
kubectl logs -n dyn-ddns -l app=dyn-ddns

# Delete deployment
kubectl delete -k k8s/overlays/production

Kustomize Overlays:

  • k8s/overlays/production - Production setup (2 replicas, higher resource limits)
  • k8s/overlays/staging - Staging environment (1 replica, relaxed rate limits)

Requirements:

  • Cert-manager (for TLS certificates via Let's Encrypt)
  • Traefik or NGINX ingress controller
  • Persistent storage class (for SQLite database)

See k8s/README.md for detailed Kubernetes deployment documentation.

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

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)

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

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

# Local development
go mod tidy
go run cmd/server/main.go

# Build
go build -o server cmd/server/main.go

# Run all tests
go test ./...

# Run tests with verbose output
go test ./... -v

# Run specific test package
go test ./internal/handlers -v
go test ./internal/database -v
go test ./tests/integration -v

# Run with coverage
go test ./... -cover

Testing

The project includes comprehensive tests:

Unit Tests

  • config: Configuration loading and validation (11 tests)
  • database: Database operations and models (9 tests)
  • handlers: Handler logic, custom filters, and validation (36 tests)

Integration Tests

Full end-to-end tests using:

  • Mock Technitium Server: Simulates DNS API without external dependencies
  • SQLite: In-memory/temp database for isolated testing
  • Gin Test Server: HTTP testing without network

Integration tests cover:

  • Space claiming workflow
  • Subdomain availability checking
  • Profanity filtering
  • Custom DWS/Tanishq Dubey trademark filtering
  • DynDNS2 protocol updates
  • Full end-to-end workflows

Test Files

internal/config/config_test.go          # Config tests
internal/database/db_test.go            # Database tests
internal/handlers/handlers_test.go      # Handler unit tests
internal/handlers/validation.go         # Custom validators
internal/testutil/mock_technitium.go    # Mock DNS server
tests/integration/integration_test.go   # Integration tests

Running Integration Tests

# Run all integration tests
go test ./tests/integration -v

# Run specific integration test
go test ./tests/integration -v -run TestIntegration_FullWorkflow

# Run with timeout
go test ./tests/integration -v -timeout 30s

License

MIT

Credits

Inspired by benjaminbear/docker-ddns-server