namedot — Lightweight DNS server with REST + GeoDNS
Overview
- UDP/TCP DNS on :53
- Zones and records in DB (GORM: Postgres/MySQL/SQLite)
- REST API for zone management (+ JSON/BIND export, JSON import)
- HTTPS support with automatic certificate reloading
- IP-based access control (CIDR whitelist)
- Geo-aware responses (subnet/country/continent), ECS support
- Optional forwarder for cache-miss
- Simple in-memory TTL cache
- Master-Slave replication via REST API
Installation
# Add repository
echo "deb [trusted=yes] https://foxzi.github.io/namedot/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/namedot.list
# Update and install
sudo apt-get update
sudo apt-get install namedot
# Start service
sudo systemctl enable namedot
sudo systemctl start namedot# Add repository
sudo curl -o /etc/yum.repos.d/namedot.repo https://foxzi.github.io/namedot/yum-repo/namedot/namedot.repo
# Install
sudo yum install namedot
# or
sudo dnf install namedot
# Start service
sudo systemctl enable namedot
sudo systemctl start namedotDownload DEB/RPM packages from Releases
After installation, all documentation is available at /usr/share/doc/namedot/:
- README.md - Main documentation
- REPLICATION.md - Replication setup guide
- DOCKER.md - Docker deployment guide
- WEBADMIN.md - Web admin panel guide
- LICENSE - MIT License
Requirements
- Go >= 1.23 (рекомендуется 1.24+)
Quick Start
- Create
config.yamlin repo root:
listen: ":53"
forwarder: "8.8.8.8"
enable_dnssec: false
api_token: "devtoken"
rest_listen: ":8080"
default_ttl: 300
soa:
auto_on_missing: true
primary: "ns1.{zone}"
hostmaster: "hostmaster.{zone}"
ns:
servers:
- "ns1.{zone}"
- "ns2.{zone}"
auto_on_missing: true
auto_fix: true
ttl: 86400
db:
driver: "sqlite"
dsn: "file:namedot.db?_foreign_keys=on"
geoip:
enabled: false
mmdb_path: "/var/lib/namedot/geoipdb"
reload_sec: 300
use_ecs: true
log:
dns_verbose: true
sql_debug: false # Set to true to log all SQL queries (for debugging)
Note: REST API requests are rejected unless api_token or api_token_hash is configured.
- Build and run:
go build ./cmd/namedotsudo ./namedot(DNS on :53 requires privileges or port redirect)
Command-line flags
-c, --config: path to config file (YAML). Example:./namedot --config config.yaml-t, --test: validate config and exit. Example:./namedot --test-p, --password: generate bcrypt hash for admin password and exit. Example:./namedot --password mySecret-g, --gen-token: generate bcrypt hash for API token and exit. Example:./namedot --gen-token myToken-v, --version: print version and exit. Example:./namedot --version
Environment and precedence
SGDNS_CONFIG: if set, used as config path when--configis not provided.- Precedence:
--config>SGDNS_CONFIG>./config.yaml.
Examples
# Run with explicit config
./namedot --config /etc/namedot/config.yaml
# Validate config and exit (no network listeners)
./namedot --test --config ./config.yaml
# Generate bcrypt hash for admin password
./namedot --password 'MyStr0ng!P@ssw0rd'
# Generate bcrypt hash for API token
./namedot --gen-token 'mySecureToken123'
# Print version
./namedot --versionREST API (Bearer devtoken)
- Base URL:
http://127.0.0.1:8080 - Auth: header
Authorization: Bearer devtoken(required)
Examples (curl)
-
Create zone
curl -sS -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"example.com"}' http://127.0.0.1:8080/zones- Capture ID (requires jq):
ZID=$(curl -sS -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"example.com"}' http://127.0.0.1:8080/zones | jq -r .id)
-
List zones
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones
-
Get zone by name (returns single zone with RRSets or 404)
curl -sS -H 'Authorization: Bearer devtoken' 'http://127.0.0.1:8080/zones?name=example.com'- Name is normalized:
EXAMPLE.COM→example.com., trailing dot is added automatically - Get zone ID by name (requires jq):
ZID=$(curl -sS -H 'Authorization: Bearer devtoken' 'http://127.0.0.1:8080/zones?name=example.com' | jq -r .id)
-
Add A rrset (www)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"www","type":"A","ttl":300,"records":[{"data":"192.0.2.10"},{"data":"192.0.2.11"}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Add AAAA rrset (www)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"www","type":"AAAA","ttl":300,"records":[{"data":"2001:db8::10"}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Add CNAME rrset (api → www.example.com.)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"api","type":"CNAME","ttl":300,"records":[{"data":"www.example.com."}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Add MX rrset (at zone apex)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"@","type":"MX","ttl":3600,"records":[{"data":"10 mail.example.com."}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Add TXT rrset (at zone apex)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"@","type":"TXT","ttl":300,"records":[{"data":"\"hello world\""}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Add Geo A rrset (svc) with selectors
- Priority: subnet > asn > country > continent > default
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"svc","type":"A","ttl":60, "records":[ {"data":"198.51.100.11","country":"US"}, {"data":"198.51.100.12"}, {"data":"198.51.100.13","subnet":"8.8.8.0/24"}, {"data":"198.51.100.14","continent":"EU"}, {"data":"198.51.100.15","asn":65001} ]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
List rrsets
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones/$ZID/rrsets
-
Update rrset (PUT) by id (example: change TTL)
curl -sS -X PUT -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"www","type":"A","ttl":120,"records":[{"data":"192.0.2.10"},{"data":"192.0.2.11"}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets/<RRSET_ID>
-
Delete rrset by id
curl -sS -X DELETE -H 'Authorization: Bearer devtoken' \ http://127.0.0.1:8080/zones/$ZID/rrsets/<RRSET_ID>
-
Export zone
- JSON:
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones/$ZID/export?format=json - BIND:
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones/$ZID/export?format=bind
- JSON:
-
Import zone
- JSON (upsert):
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ --data-binary @zone.json "http://127.0.0.1:8080/zones/$ZID/import?format=json&mode=upsert" - BIND (replace):
curl -sS -X POST -H 'Authorization: Bearer devtoken' --data-binary @zone.bind \ "http://127.0.0.1:8080/zones/$ZID/import?format=bind&mode=replace"
- JSON (upsert):
Replication
- Master-Slave replication via REST API with automatic sync
- See REPLICATION.md for setup and configuration
- Example configs: examples/config.master.yaml and examples/config.slave.yaml
Notes
- DNSSEC dynamic signing is not implemented yet. You can store DNSSEC records (DNSKEY/RRSIG/DS) in DB and serve them as-is when queried.
- Geo selection currently supports subnet/country/continent attributes on records. ASN requires GeoIP DB integration and is a TODO.
GeoIP with Auto-Download
- Enable in config:
geoip.enabled: truegeoip.mmdb_path: <path to .mmdb file or directory>geoip.use_ecs: trueto honor EDNS Client Subnetgeoip.download_urls: [list of URLs]for automatic MMDB downloadsgeoip.download_interval_sec: 86400for periodic updates (24 hours)
Supported Formats:
- MaxMind GeoLite2: GeoLite2-Country.mmdb, GeoLite2-ASN.mmdb (via geoip2 library)
- DBIP: dbip-country-ipv4.mmdb, dbip-country-ipv6.mmdb, dbip-asn-*.mmdb (via maxminddb library)
The server automatically detects format and database type from file metadata. Only Country and ASN databases are supported (City databases are not needed for GeoDNS).
Configuration Example:
geoip:
enabled: true
mmdb_path: "/var/lib/namedot/geoipdb"
reload_sec: 300 # Reload databases every 5 minutes
use_ecs: true
download_urls:
- "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb"
- "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb"
download_interval_sec: 86400 # Download updates every 24 hoursAuto-Download Features:
- Initial Download: If
mmdb_pathdirectory is empty anddownload_urlsare configured, files are downloaded on startup - Periodic Updates: Files are re-downloaded automatically based on
download_interval_sec - Hot Reload: After download, databases are automatically reloaded without service restart
- Docker-Friendly: No cron needed - everything works automatically inside containers
DBIP Alternative (free, updated weekly):
geoip:
enabled: true
mmdb_path: "/var/lib/namedot/geoipdb"
reload_sec: 300
use_ecs: true
download_urls:
- "https://github.com/sapics/ip-location-db/raw/refs/heads/main/dbip-country-mmdb/dbip-country-ipv4.mmdb"
- "https://github.com/sapics/ip-location-db/raw/refs/heads/main/dbip-country-mmdb/dbip-country-ipv6.mmdb"
download_interval_sec: 604800 # Weekly updatesLogs: on startup and during downloads, server logs detailed progress including file sizes, success/failure status, and which GeoIP DBs are loaded.
BIND Import
- REST:
POST /zones/{id}/import?format=bind&mode=upsert|replacewith raw zone text in body. - Export remains available via
GET /zones/{id}/export?format=bind.
Testing
- Unit tests (modules):
- BIND import/export:
go test ./internal/server/rest/zoneio -run TestImportBIND_And_ToBind -count=1
- BIND import/export:
- All tests:
go test ./... - Tests use in-memory SQLite, сетевые сервисы не поднимаются.
Integration Tests
- End-to-end (REST + DNS):
go test ./internal/integration -run TestEndToEnd_DNS_and_REST -count=1- Под капотом: поднимает DNS на 19053 и REST на 18089, создаёт зону и A-запись через REST, затем делает DNS-запрос и проверяет ответ, включая повторный запрос (кэш).
- GeoDNS (requires ./geoipdb with .mmdb files):
- Subnet/ECS selection:
go test ./internal/integration -run TestGeoDNS_WithECS_USCountry -count=1 - Country/Continent/ASN selection (auto-skips if data missing):
go test ./internal/integration -run TestGeoDNS_WithECS_Country_Continent_ASN -count=1
- Subnet/ECS selection:
GeoIP Databases
- Repo ships small MMDBs for local tests in
./geoipdb:- IPv4 localhost ranges:
city-localhost.mmdb,asn-localhost.mmdb(127.0.1.0/24 → RU/EU/AS65001, 127.0.2.0/24 → GB/EU/AS65002). - IPv6 documentation ranges:
city-localhost6.mmdb,asn-localhost6.mmdb(2001:db8:1::/64 and 2001:db8:2::/64) for ECS IPv6 tests.
- IPv4 localhost ranges:
- Verify with
mmdblookup, e.g.:mmdblookup --file geoipdb/city-localhost.mmdb --ip 127.0.1.10mmdblookup --file geoipdb/asn-localhost6.mmdb --ip 2001:db8:1::1
Development
- Sync deps:
go mod tidy - Build only main:
go build ./cmd/namedot - Lint/format: follow project defaults (no external config added yet)
Makefile
- Build server:
make build - Run with config:
make run CFG=config.yaml - All tests:
make test-all - Unit + integration:
make test - GeoDNS tests:
make test-geo
Building Packages
To build DEB/RPM packages locally:
- Install nFPM:
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list
sudo apt-get update
sudo apt-get install -y nfpm- Build packages using Makefile (recommended):
# Build both DEB and RPM packages
# Version is automatically detected from git tags
make package
# Or build specific package type
make package-deb
make package-rpm
# Override version if needed
make package VERSION=0.2.0The Makefile automatically:
- Builds the binary with correct version and flags
- Creates DEB and RPM packages
- Shows package sizes and locations
- Manual build (alternative):
# Set version
export VERSION="0.1.0"
# Build binary
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v \
-ldflags "-X main.Version=$VERSION -s -w" \
-o namedot \
./cmd/namedot
# Build packages
VERSION=$VERSION nfpm pkg --packager deb --config packaging/nfpm.yaml --target .
VERSION=$VERSION nfpm pkg --packager rpm --config packaging/nfpm.yaml --target .Config Reference
soa.auto_on_missing: if true, при отсутствии SOA в зоне автоматически создаётся дефолтная запись SOA:- MNAME:
soa.primary(по умолчаниюns1.<zone>.) - RNAME:
soa.hostmaster(по умолчаниюhostmaster.<zone>.) - SERIAL: текущий Unix timestamp
- Refresh/Retry/Expire/Minimum: 7200/3600/1209600/300
- TTL: 3600
- MNAME:
ns.servers: list of default NS server names for new zones. Supports{zone}placeholder (e.g.,ns1.{zone}→ns1.example.com.)ns.auto_on_missing: if true, NS records are automatically created when a new zone is createdns.auto_fix: if true, missing NS records are automatically added to existing zones on server startupns.ttl: TTL for auto-created NS records (default: 86400)default_ttl: TTL по умолчанию для записей/наборов, где TTL не указан (или равен 0). Используется в JSON/BIND импорте.
Security Features
Enable HTTPS for REST API and Web Admin interface:
rest_listen: ":8443"
tls_cert_file: "/etc/ssl/certs/server.crt"
tls_key_file: "/etc/ssl/private/server.key"
tls_reload_sec: 3600 # Reload certificate every hour (default: 3600)Features:
- Automatic Certificate Reloading: Certificates are periodically reloaded from disk without service restart
- Hot Reload: Perfect for Let's Encrypt and other auto-renewed certificates
- TLS 1.2+: Minimum TLS version enforced for security
- Backward Compatible: If TLS not configured, server runs in HTTP mode
Example with Let's Encrypt:
rest_listen: ":443"
tls_cert_file: "/etc/letsencrypt/live/dns.example.com/fullchain.pem"
tls_key_file: "/etc/letsencrypt/live/dns.example.com/privkey.pem"
tls_reload_sec: 3600Restrict REST API access to specific IP ranges using CIDR notation:
allowed_cidrs:
- "127.0.0.0/8" # Localhost
- "10.0.0.0/8" # Private network
- "192.168.1.0/24" # Local subnet
- "2001:db8::/32" # IPv6 networkFeatures:
- IPv4 and IPv6: Full support for both IP versions
- Multiple Networks: Specify multiple CIDR blocks
- Secure by Default: Empty list allows all (backward compatible)
- Automatic Validation: Invalid CIDRs are rejected at startup
- Reverse Proxy: ACL checks the direct peer IP. If you run behind a proxy, allow the proxy IPs at the ACL or put ACL rules in the proxy/firewall.
If allowed_cidrs is not specified or empty, all IPs are allowed (default behavior).
SmaillGeoDNS — Легковесный DNS-сервер с REST API + GeoDNS
- UDP/TCP DNS на порту :53
- Зоны и записи в БД (GORM: Postgres/MySQL/SQLite)
- REST API для управления зонами (+ JSON/BIND экспорт, JSON импорт)
- Поддержка HTTPS с автоматической перезагрузкой сертификатов
- Контроль доступа по IP (whitelist на основе CIDR)
- Geo-aware ответы (подсеть/страна/континент), поддержка ECS
- Опциональный форвардер при отсутствии записи в кеше
- Простой in-memory TTL кеш
- Master-Slave репликация через REST API
# Добавить репозиторий
echo "deb [trusted=yes] https://foxzi.github.io/namedot/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/namedot.list
# Обновить и установить
sudo apt-get update
sudo apt-get install namedot
# Запустить сервис
sudo systemctl enable namedot
sudo systemctl start namedot# Добавить репозиторий
sudo curl -o /etc/yum.repos.d/namedot.repo https://foxzi.github.io/namedot/yum-repo/namedot/namedot.repo
# Установить
sudo yum install namedot
# или
sudo dnf install namedot
# Запустить сервис
sudo systemctl enable namedot
sudo systemctl start namedotСкачайте DEB/RPM пакеты из Releases
После установки вся документация доступна в /usr/share/doc/namedot/:
- README.md - Основная документация
- REPLICATION.md - Руководство по репликации
- DOCKER.md - Руководство по Docker
- WEBADMIN.md - Руководство по веб-панели
- LICENSE - Лицензия MIT
- Go >= 1.23 (рекомендуется 1.24+)
- Создайте
config.yamlв корне репозитория:
listen: ":53"
forwarder: "8.8.8.8"
enable_dnssec: false
api_token: "devtoken"
rest_listen: ":8080"
default_ttl: 300
soa:
auto_on_missing: true
primary: "ns1.{zone}"
hostmaster: "hostmaster.{zone}"
ns:
servers:
- "ns1.{zone}"
- "ns2.{zone}"
auto_on_missing: true
auto_fix: true
ttl: 86400
db:
driver: "sqlite"
dsn: "file:namedot.db?_foreign_keys=on"
geoip:
enabled: false
mmdb_path: "/var/lib/namedot/geoipdb"
reload_sec: 300
use_ecs: true
log:
dns_verbose: true
sql_debug: false # Установите true для логирования SQL запросов (отладка)- Сборка и запуск:
go build ./cmd/namedotsudo ./namedot(DNS на :53 требует привилегий или проброса порта)
CLI флаги
-c, --config: путь к конфигу (YAML). Пример:./namedot --config config.yaml-t, --test: проверить конфиг и выйти. Пример:./namedot --test-p, --password: сгенерировать bcrypt-хеш для пароля админки и выйти. Пример:./namedot --password mySecret-g, --gen-token: сгенерировать bcrypt-хеш для API токена и выйти. Пример:./namedot --gen-token myToken-v, --version: вывести версию и выйти. Пример:./namedot --version
Окружение и приоритеты
SGDNS_CONFIG: если установлен, используется как путь к конфигу при отсутствии--config.- Приоритет:
--config>SGDNS_CONFIG>./config.yaml.
Примеры
# Запуск с явным конфигом
./namedot --config /etc/namedot/config.yaml
# Проверка конфига и выход (без запуска сетевых слушателей)
./namedot --test --config ./config.yaml
# Генерация bcrypt-хеша для пароля админки
./namedot --password 'MyStr0ng!P@ssw0rd'
# Генерация bcrypt-хеша для API токена
./namedot --gen-token 'mySecureToken123'
# Вывести версию
./namedot --version- Базовый URL:
http://127.0.0.1:8080 - Аутентификация: заголовок
Authorization: Bearer devtoken
Примеры (curl)
-
Создать зону
curl -sS -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"example.com"}' http://127.0.0.1:8080/zones- Сохранить ID (требуется jq):
ZID=$(curl -sS -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"example.com"}' http://127.0.0.1:8080/zones | jq -r .id)
-
Список зон
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones
-
Получить зону по имени (возвращает одну зону с RRSets или 404)
curl -sS -H 'Authorization: Bearer devtoken' 'http://127.0.0.1:8080/zones?name=example.com'- Имя нормализуется:
EXAMPLE.COM→example.com., точка в конце добавляется автоматически - Получить ID зоны по имени (требуется jq):
ZID=$(curl -sS -H 'Authorization: Bearer devtoken' 'http://127.0.0.1:8080/zones?name=example.com' | jq -r .id)
-
Добавить A rrset (www)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"www","type":"A","ttl":300,"records":[{"data":"192.0.2.10"},{"data":"192.0.2.11"}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Добавить AAAA rrset (www)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"www","type":"AAAA","ttl":300,"records":[{"data":"2001:db8::10"}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Добавить CNAME rrset (api → www.example.com.)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"api","type":"CNAME","ttl":300,"records":[{"data":"www.example.com."}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Добавить MX rrset (на корне зоны)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"@","type":"MX","ttl":3600,"records":[{"data":"10 mail.example.com."}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Добавить TXT rrset (на корне зоны)
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"@","type":"TXT","ttl":300,"records":[{"data":"\"hello world\""}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Добавить Geo A rrset (svc) с селекторами
- Приоритет: subnet > asn > country > continent > default
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"svc","type":"A","ttl":60, "records":[ {"data":"198.51.100.11","country":"US"}, {"data":"198.51.100.12"}, {"data":"198.51.100.13","subnet":"8.8.8.0/24"}, {"data":"198.51.100.14","continent":"EU"}, {"data":"198.51.100.15","asn":65001} ]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets
-
Список rrset
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones/$ZID/rrsets
-
Обновить rrset (PUT) по id (например, сменить TTL)
curl -sS -X PUT -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ -d '{"name":"www","type":"A","ttl":120,"records":[{"data":"192.0.2.10"},{"data":"192.0.2.11"}]}' \ http://127.0.0.1:8080/zones/$ZID/rrsets/<RRSET_ID>
-
Удалить rrset по id
curl -sS -X DELETE -H 'Authorization: Bearer devtoken' \ http://127.0.0.1:8080/zones/$ZID/rrsets/<RRSET_ID>
-
Экспорт зоны
- JSON:
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones/$ZID/export?format=json - BIND:
curl -sS -H 'Authorization: Bearer devtoken' http://127.0.0.1:8080/zones/$ZID/export?format=bind
- JSON:
-
Импорт зоны
- JSON (upsert):
curl -sS -X POST -H 'Authorization: Bearer devtoken' -H 'Content-Type: application/json' \ --data-binary @zone.json "http://127.0.0.1:8080/zones/$ZID/import?format=json&mode=upsert" - BIND (replace):
curl -sS -X POST -H 'Authorization: Bearer devtoken' --data-binary @zone.bind \ "http://127.0.0.1:8080/zones/$ZID/import?format=bind&mode=replace"
- JSON (upsert):
- Master-Slave репликация через REST API с автоматической синхронизацией
- См. REPLICATION.md для настройки и конфигурации
- Примеры конфигов: examples/config.master.yaml и examples/config.slave.yaml
- Динамическая подпись DNSSEC пока не реализована. Вы можете хранить DNSSEC-записи (DNSKEY/RRSIG/DS) в БД и отдавать их как есть при запросе.
- Geo-выбор в настоящее время поддерживает атрибуты subnet/country/continent на записях. ASN требует интеграции GeoIP DB и находится в TODO.
- Включить в конфиге:
geoip.enabled: truegeoip.mmdb_path: <путь к .mmdb файлу или директории>geoip.use_ecs: trueдля учета EDNS Client Subnetgeoip.download_urls: [список URL]для автоматического скачивания MMDBgeoip.download_interval_sec: 86400для периодических обновлений (24 часа)
Поддерживаемые форматы:
- MaxMind GeoLite2: GeoLite2-Country.mmdb, GeoLite2-ASN.mmdb (через библиотеку geoip2)
- DBIP: dbip-country-ipv4.mmdb, dbip-country-ipv6.mmdb, dbip-asn-*.mmdb (через библиотеку maxminddb)
Сервер автоматически определяет формат и тип базы данных по метаданным файла. Поддерживаются только Country и ASN базы (City базы не нужны для GeoDNS).
Пример конфигурации:
geoip:
enabled: true
mmdb_path: "/var/lib/namedot/geoipdb"
reload_sec: 300 # Перезагрузка баз каждые 5 минут
use_ecs: true
download_urls:
- "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb"
- "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb"
download_interval_sec: 86400 # Скачивание обновлений каждые 24 часаВозможности автоскачивания:
- Начальное скачивание: Если директория
mmdb_pathпуста и настроеныdownload_urls, файлы скачиваются при запуске - Периодические обновления: Файлы автоматически перекачиваются согласно
download_interval_sec - Горячая перезагрузка: После скачивания базы автоматически перезагружаются без перезапуска сервиса
- Docker-совместимость: Не нужен cron - всё работает автоматически внутри контейнеров
Альтернатива DBIP (бесплатная, обновляется еженедельно):
geoip:
enabled: true
mmdb_path: "/var/lib/namedot/geoipdb"
reload_sec: 300
use_ecs: true
download_urls:
- "https://github.com/sapics/ip-location-db/raw/refs/heads/main/dbip-country-mmdb/dbip-country-ipv4.mmdb"
- "https://github.com/sapics/ip-location-db/raw/refs/heads/main/dbip-country-mmdb/dbip-country-ipv6.mmdb"
download_interval_sec: 604800 # Еженедельные обновленияЛоги: при запуске и во время скачивания сервер выводит детальный прогресс, включая размеры файлов, статус успеха/неудачи и информацию о загруженных GeoIP базах.
- REST:
POST /zones/{id}/import?format=bind&mode=upsert|replaceс сырым текстом зоны в теле. - Экспорт остаётся доступен через
GET /zones/{id}/export?format=bind.
- Модульные тесты (модули):
- BIND импорт/экспорт:
go test ./internal/server/rest/zoneio -run TestImportBIND_And_ToBind -count=1
- BIND импорт/экспорт:
- Все тесты:
go test ./... - Тесты используют in-memory SQLite, сетевые сервисы не поднимаются.
- End-to-end (REST + DNS):
go test ./internal/integration -run TestEndToEnd_DNS_and_REST -count=1- Под капотом: поднимает DNS на 19053 и REST на 18089, создаёт зону и A-запись через REST, затем делает DNS-запрос и проверяет ответ, включая повторный запрос (кэш).
- GeoDNS (требует ./geoipdb с .mmdb файлами):
- Subnet/ECS выбор:
go test ./internal/integration -run TestGeoDNS_WithECS_USCountry -count=1 - Country/Continent/ASN выбор (авто-пропуск при отсутствии данных):
go test ./internal/integration -run TestGeoDNS_WithECS_Country_Continent_ASN -count=1
- Subnet/ECS выбор:
- Репозиторий содержит небольшие MMDB для локальных тестов в
./geoipdb:- IPv4 localhost диапазоны:
city-localhost.mmdb,asn-localhost.mmdb(127.0.1.0/24 → RU/EU/AS65001, 127.0.2.0/24 → GB/EU/AS65002). - IPv6 документационные диапазоны:
city-localhost6.mmdb,asn-localhost6.mmdb(2001:db8:1::/64 и 2001:db8:2::/64) для ECS IPv6 тестов.
- IPv4 localhost диапазоны:
- Проверка с помощью
mmdblookup, например:mmdblookup --file geoipdb/city-localhost.mmdb --ip 127.0.1.10mmdblookup --file geoipdb/asn-localhost6.mmdb --ip 2001:db8:1::1
- Синхронизация зависимостей:
go mod tidy - Сборка только main:
go build ./cmd/namedot - Линтинг/форматирование: следуйте дефолтным настройкам проекта (внешний конфиг пока не добавлен)
- Сборка сервера:
make build - Запуск с конфигом:
make run CFG=config.yaml - Все тесты:
make test-all - Модульные + интеграционные:
make test - GeoDNS тесты:
make test-geo
Для локальной сборки DEB/RPM пакетов:
- Установить nFPM:
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list
sudo apt-get update
sudo apt-get install -y nfpm- Собрать пакеты через Makefile (рекомендуется):
# Собрать оба пакета DEB и RPM
# Версия автоматически определяется из git tags
make package
# Или собрать конкретный тип пакета
make package-deb
make package-rpm
# Переопределить версию при необходимости
make package VERSION=0.2.0Makefile автоматически:
- Собирает бинарник с правильной версией и флагами
- Создаёт DEB и RPM пакеты
- Показывает размеры пакетов и их расположение
- Ручная сборка (альтернатива):
# Установить версию
export VERSION="0.1.0"
# Собрать бинарник
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v \
-ldflags "-X main.Version=$VERSION -s -w" \
-o namedot \
./cmd/namedot
# Собрать пакеты
VERSION=$VERSION nfpm pkg --packager deb --config packaging/nfpm.yaml --target .
VERSION=$VERSION nfpm pkg --packager rpm --config packaging/nfpm.yaml --target .soa.auto_on_missing: если true, при отсутствии SOA в зоне автоматически создаётся дефолтная запись SOA:- MNAME:
soa.primary(по умолчаниюns1.<zone>.) - RNAME:
soa.hostmaster(по умолчаниюhostmaster.<zone>.) - SERIAL: текущий Unix timestamp
- Refresh/Retry/Expire/Minimum: 7200/3600/1209600/300
- TTL: 3600
- MNAME:
ns.servers: список NS-серверов по умолчанию для новых зон. Поддерживает плейсхолдер{zone}(например,ns1.{zone}→ns1.example.com.)ns.auto_on_missing: если true, NS-записи автоматически создаются при создании новой зоныns.auto_fix: если true, отсутствующие NS-записи автоматически добавляются в существующие зоны при запуске сервераns.ttl: TTL для автоматически создаваемых NS-записей (по умолчанию: 86400)default_ttl: TTL по умолчанию для записей/наборов, где TTL не указан (или равен 0). Используется в JSON/BIND импорте.
Включение HTTPS для REST API и веб-админки:
rest_listen: ":8443"
tls_cert_file: "/etc/ssl/certs/server.crt"
tls_key_file: "/etc/ssl/private/server.key"
tls_reload_sec: 3600 # Перезагрузка сертификата каждый час (по умолчанию: 3600)Возможности:
- Автоматическая перезагрузка сертификатов: Сертификаты периодически перечитываются с диска без перезапуска сервиса
- Горячая перезагрузка: Идеально для Let's Encrypt и других автообновляемых сертификатов
- TLS 1.2+: Минимальная версия TLS для безопасности
- Обратная совместимость: Если TLS не настроен, сервер работает в режиме HTTP
Пример с Let's Encrypt:
rest_listen: ":443"
tls_cert_file: "/etc/letsencrypt/live/dns.example.com/fullchain.pem"
tls_key_file: "/etc/letsencrypt/live/dns.example.com/privkey.pem"
tls_reload_sec: 3600Ограничение доступа к REST API по определённым IP-диапазонам в нотации CIDR:
allowed_cidrs:
- "127.0.0.0/8" # Localhost
- "10.0.0.0/8" # Приватная сеть
- "192.168.1.0/24" # Локальная подсеть
- "2001:db8::/32" # IPv6 сетьВозможности:
- IPv4 и IPv6: Полная поддержка обеих версий IP
- Множественные сети: Можно указать несколько CIDR блоков
- Безопасно по умолчанию: Пустой список разрешает всем (обратная совместимость)
- Автоматическая валидация: Неправильные CIDR отклоняются при запуске
- Reverse Proxy: ACL проверяет прямой IP подключения. Если стоит прокси, добавьте IP прокси в ACL или настраивайте ограничения в прокси/фаерволе.
Если allowed_cidrs не указан или пуст, доступ разрешён всем IP (поведение по умолчанию).
This project is licensed under the MIT License - see the LICENSE file for details.