https://github.com/addspin/hllb
High load DNS server for load balancing between balancers or other systems
https://github.com/addspin/hllb
balancing checker dns forward high-load upstream
Last synced: 2 months ago
JSON representation
High load DNS server for load balancing between balancers or other systems
- Host: GitHub
- URL: https://github.com/addspin/hllb
- Owner: addspin
- License: mit
- Created: 2026-02-07T20:18:00.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-04-05T21:19:02.000Z (2 months ago)
- Last Synced: 2026-04-05T23:21:43.923Z (2 months ago)
- Topics: balancing, checker, dns, forward, high-load, upstream
- Language: Go
- Homepage:
- Size: 4.48 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# HLLB - High load DNS сервер с проверкой хостов для балансировки нагрузки
## Быстрый старт
Для балансировки нагрузки:
- Добавьте несколько одинаковых "A" записей в зоне с разными ip адресами.
- В файле `check.yaml` добавьте список этих ip для проверки.
- Запустите сервер.
- curl на запись к которой вы закрепили ip ареса в зоне будет чередовать выдачу ip адреса из списка `check.yaml`.
---
### Некторые варианты работы
```
┌──────────────────────────────────────────────────────────────────┐
│ ИНТЕРНЕТ │
│ │
│ Пользователь ──────▶ DNS Провайдер │
│ │ │ │
│ │ │ NS: ns1.your-domain.com. │
│ │ │ → 203.0.113.1 (hllb) │
│ │ ▼ │
│ │ ┌─────────────────┐ │
│ │ │ Ваш hllb │ │
│ │ │ 203.0.113.1 │ │
│ │ │ │ │
│ │ │ check.yaml: │ │
│ │ │ - 198.51.100.1 │ ← внешний IP │
│ │ │ - 198.51.100.2 │ ← внешний IP │
│ │ │ - 198.51.100.3 │ ← внешний IP │
│ │ │ │ │
│ │ │ zone/your-domain.com: │
│ │ │ lb A 198.51.100.1 │
│ │ │ lb A 198.51.100.2 │
│ │ │ lb A 198.51.100.3 │
│ │ └─────────────────┘ │
│ │ │ │
│ │ │ Health Check (TCP) │
│ │ │ по внешним IP │
│ ▼ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ Firewall / NAT Gateway │ │
│ │ (прозрачно для hllb) │ │
│ │ 198.51.100.1:443 → 10.0.1.1:80 │ │
│ │ 198.51.100.2:443 → 10.0.1.2:80 │ │
│ │ 198.51.100.3:443 → 10.0.1.3:80 │ │
│ └──────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Закрытый контур (VPN) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Клиент #1 │ │ Клиент #2 │ │ Клиент #3 │ │
│ │ 10.100.1.10 │ │ 10.100.1.11 │ │ 10.100.1.12 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ │ DNS запрос (внутри VPN) │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Ваш hllb │ │
│ │ 10.100.1.1 │ │
│ │ :53 (DNS) │ │
│ │ │ │
│ │ check.yaml: │ │
│ │ - 10.200.1.1 │ │
│ │ - 10.200.1.2 │ │
│ │ - 10.200.1.3 │ │
│ │ port: 80 │ │
│ │ │ │
│ │ zone/internal: │ │
│ │ lb A 10.200.1.1 │
│ │ lb A 10.200.1.2 │
│ │ lb A 10.200.1.3 │
│ └────────┬────────┘ │
│ │ │
│ │ Health Check (TCP) │
│ │ по внутренним IP │
│ ▼ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │Balancer #1 │ │Balancer #2 │ │Balancer #3 │ │
│ │10.200.1.1 │ │10.200.1.2 │ │10.200.1.3 │ │
│ │ :80 │ │ :80 │ │ :80 │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Сборка
```bash
go build -o hllb .
```
### Первый запуск
При первом запуске автоматически создаются отсутствующие файлы:
- `config.yaml` — конфигурация сервера (порт 53, forward на 8.8.8.8)
- `check.yaml` — список хостов для health check
- `zone/example.com` — тестовая зона
```bash
./hllb
```
### Запуск на порту 53
Порт 53 — привилегированный (< 1024), для работы на нём требуются дополнительные права.
#### Linux
```bash
# Рекомендуемый способ — дать бинарнику право на привилегированные порты
sudo setcap 'cap_net_bind_service=+ep' ./hllb
./hllb
# Или запуск от root (не рекомендуется)
sudo ./hllb
```
Если порт 53 уже занят `systemd-resolved`:
```bash
sudo ss -tlnp | grep :53
sudo systemctl disable --now systemd-resolved
```
#### macOS
```bash
# Запуск от root
sudo ./hllb
```
Альтернатива — слушать на высоком порту и пробросить через pfctl:
```bash
# В config.yaml: port: 1053
echo "rdr pass on lo0 inet proto {tcp, udp} from any to 127.0.0.1 port 53 -> 127.0.0.1 port 1053" | sudo pfctl -ef -
```
#### Windows
Порты ниже 1024 в Windows не привилегированные — дополнительных прав не требуется. Убедитесь, что порт 53 не занят службой DNS Client:
```powershell
netstat -ano | findstr :53
net stop "DNS Client"
```
### Проверка работы
```bash
dig @127.0.0.1 -p 53 example.com A
```
---
## Возможности
### DNS-сервер
- Слушает входящие запросы одновременно по **UDP и TCP** на настраиваемом порту
- Обрабатывает типы запросов **A** и **NS**
- Флаг **Authoritative** устанавливается только для ответов из собственных зон
- **Форвардинг** — запросы, не найденные в зонах, пересылаются на внешний DNS (настраивается в `config.yaml`)
### Зоны и записи
Файлы зон хранятся в директории `./zone/`. Имя файла — имя зоны (например `test.ru`), оно же становится `ORIGIN` — корневым доменом, подставляемым к относительным записям внутри файла.
Поддерживаемые типы записей в зонах:
| Тип | Пример в zone-файле | Описание |
|-----|---------------------|----------|
| `A` | `www IN A 10.13.1.34` | IPv4-адрес |
| `NS` | `@ IN NS ns1.test.ru.` | Сервер имён |
### Wildcard-матчинг (три уровня)
| Паттерн | Пример запроса | Описание |
|---------|----------------|----------|
| `*` (корень зоны) | `any.test.ru` | Любой поддомен зоны |
| `*.suffix` | `x.info.test.ru` | Любой поддомен для `*.info.test.ru` |
| `*.sub.domain` | `a.b.msg.admin.test.ru` | Любая вложенность субдоменов |
| `exact.domain` | `sub.admin.test.ru` | Точное совпадение записи |
Порядок приоритета обработки запроса:
1. Точное совпадение
2. Wildcard-записи зоны (`*.suffix`)
3. Wildcard-фолбэк (`*.zone`)
4. Форвардинг на внешний DNS (если `forward: true`)
5. `NXDOMAIN`
### Горячая перезагрузка зон
- Каждый zone-файл отслеживается в отдельной горутине
- Изменение файла обнаруживается через **SHA-256 хеш**
- При изменении — зона атомарно перезагружается без перезапуска сервера
- Интервал проверки настраивается: `checkZoneInterval` + `checkZoneIntervalType`
### Активная проверка хостов (Health Check)
Включается параметром `activeCheck: true` в `config.yaml`.
- Выполняет **TCP-проверку** порта для списка хостов из `check.yaml`
- При изменении - список хостов для провери и порт перезагружаются `check.yaml` (также через SHA-256)
- Алгоритм балансировки **Round Robin** применяется только к записям, чьи IP находятся в пуле `check.yaml`
- Записи, не относящиеся к пулу, отдаются напрямую из зоны
- Интервалы настраиваются отдельно: `repeatCheckInterval` и `repeatCheckFileInterval`
### Форвардинг
Включается параметром `forward: true` в `config.yaml`.
- Запросы, не найденные ни в одной зоне, пересылаются на указанный внешний DNS-сервер
- Настраивается адрес (`forwardDNS`) и порт (`forwardDNSPort`)
---
## Конфигурация
### `config.yaml`
```yaml
app:
port: 53 # Порт DNS-сервера
checkZoneInterval: 5 # Интервал проверки изменений зон
checkZoneIntervalType: seconds # Единица: seconds / minutes / hours
activeCheck: true # Включить активную проверку хостов
algorithmCheck: RR # Алгоритм балансировки (RR — Round Robin)
repeatCheckInterval: 3 # Интервал TCP-проверки хостов
repeatCheckIntervalType: seconds
repeatCheckFileInterval: 3 # Интервал проверки изменений check.yaml
repeatCheckFileIntervalType: seconds
forward: true # Включить форвардинг для неизвестных зон
forwardDNS: 8.8.8.8 # Адрес upstream DNS
forwardDNSPort: 53 # Порт upstream DNS
```
### `check.yaml`
```yaml
hostCheck:
- 10.13.1.36 # IP хоста (должен совпадать с A-записью в зоне)
- 176.125.254.184
portCheck: 22 # TCP-порт для проверки
```
---
## Benchmark
### Стенд
| | Машина | Роль |
|---|--------|------|
| Клиент | Mac mini M2, 16 GB | Генератор нагрузки (dnsperf / resperf) |
| Сервер | MacBook Air M4, 16 GB | hllb на порту 1053 |
| Сеть | Wi-Fi | Без оптимизации сетевого стека |
### resperf (поиск точки насыщения)
```
resperf -P 20260325-1333.gnuplot -s 10.13.1.18 -p 1053 -d test.info -R -C 10
```
| Метрика | Значение |
|---------|----------|
| Запросов отправлено | 673 250 |
| Запросов завершено | 608 827 (90.4%) |
| Потеряно | 64 423 (9.6%) |
| **Пиковый throughput** | **35 790 qps** |
| Потери на пике | 16.61% |
### dnsperf (стабильная нагрузка)
```
dnsperf -s 10.13.1.18 -m udp -p 1053 -d test.info -c 10 -l 30
```
| Метрика | Значение |
|---------|----------|
| Запросов отправлено | 202 632 |
| Запросов завершено | 202 632 (100%) |
| Потеряно | 0 (0%) |
| **QPS** | **6 752** |
| Средняя латенция | 14.1 ms |
| Мин / Макс латенция | 3.1 ms / 145 ms |
### Выводы
- При стабильной нагрузке (10 клиентов) — **0% потерь**, ~6 750 qps
- Пиковый throughput по resperf — **~35 800 qps** (с потерями 16%)
- Предположительно Узкое место — UDP syscall ядра macOS (если опираться на pprof)
---
## Структура проекта
```
.
├── main.go # Точка входа, запуск DNS-сервера
├── config.yaml # Основная конфигурация
├── check.yaml # Список хостов для health check
├── zone/ # DNS zone-файлы (стандартный формат RFC 1035)
│ └── test.ru
├── algorithm/
│ └── rr.go # Round Robin балансировка
├── checks/
│ └── tcpCheck.go # TCP health check
├── handles/
│ ├── handleDNS.go # Обработчик DNS-запросов
│ └── forwardDNS.go # Форвардинг на upstream DNS
└── utils/
├── initFiles.go # Создание файлов/папок при первом запуске
├── zoneParser.go # Парсинг zone-файлов
├── watchZoneFile.go # Слежение за изменениями zone-файлов
├── watchCheckFile.go # Слежение за изменениями check.yaml
├── hashFile.go # SHA-256 хеширование файлов
├── readConfig.go # Чтение и кэширование config.yaml
├── readCheck.go # Чтение check.yaml
└── selectTime.go # Конвертация единиц времени
```