# Предельные размеры watchlist в hrneo
**Версия кода:** hrneo 3.11.0-1
---
## 1. Жёсткие лимиты, заданные в коде
### 1.1 Основные лимиты (`include/hrneo.h`)
| Параметр | Значение | Назначение |
|----------|----------|------------|
| `DOMAIN_HT_BUCKETS` | 8192 | Бакетов в хеш-таблице доменов |
| `POOL_CHUNK_SIZE` | 256 КБ | Размер одного чанка пула (linked list растёт без верхнего предела) |
| `MAX_POLICY_ORDER` | 64 | Максимум уникальных политик |
| `MAX_INTERFACES` | 64 | Максимум интерфейсов DirectRoute |
| `MAX_GEO_FILES` | 16 | Максимум файлов `GeoIPFile` / `GeoSiteFile` |
| `MAX_TAG_LEN` | 64 | Максимум длины имени geosite/geoip тега (с `\0`) |
| `MAX_POLICY_NAME` | 64 | Максимум длины имени политики (с `\0`) |
| `MAX_PATH_LEN` | 512 | Максимум длины путей (`watchlistPath`, `CIDRfile`, `logfile`, `GeoIPFile=`, `GeoSiteFile=`) |
| `MAX_INTERFACE_NAME` | 32 | Максимум длины имени интерфейса (`l7WanInterface`, `interface_info_t.name`) |
| `MAX_CNAME_CHAIN` | 16 | Глубина BFS по CNAME-цепочке |
| `IPSET_DEFAULT_MAXELEM` | 262 144 | Записей на один ipset (kernel-default, fallback при `IpsetMaxElem=0`) |
| `IPSET_CHUNK_SIZE` | 256 | Размер батча `ipset_add_batch` (send N → recv N) |
| `SOCKET_READ_BUFFER` | 1 МБ | `SO_RCVBUF` для AF_PACKET-сокетов |
### 1.2 ipset-менеджер (`include/ipset_nl.h`)
| Параметр | Значение | Назначение |
|----------|----------|------------|
| `IPSET_MAX_SETS` | 512 | Кэш имён существующих ipset (`set_names[512][64]` в `ipset_manager_t`) |
| `set_has_timeout[256]` | 256 | Кэш timeout-режима по FNV-1a индексу (`name & 0xFF`) |
| `ipset_name_cache[MAX_POLICY_ORDER]` | 64 | Уникальных имён ipset в хеш-таблице доменов |
### 1.3 GeoIP/GeoSite + CIDR-миграция (`src/geodat.c`)
| Параметр | Значение | Назначение |
|----------|----------|------------|
| `NAME_INDEX_SLOTS` | 256 | Open-addressed FNV-1a индексы для `batches[]` / `usage[]` |
| `CIDR_MIGRATE_MAX_LINES` | 16 384 | Лимит строк в `ip.list` при автомиграции oversized geoip |
| `CIDR_MIGRATE_MAX_BLOCKS` | 512 | Лимит блоков `/Name` в `ip.list` при автомиграции |
| `geosite_rule_t gs_rules[256]` | 256 | Лимит строк `geosite:TAG/...` в `domain.conf` (буфер в `main.c`) |
### 1.4 DNS-канал (`include/dns.h`)
| Параметр | Значение | Назначение |
|----------|----------|------------|
| `DNS_MAX_ANSWERS` | 128 | Максимум A/AAAA-записей в одном DNS-ответе |
| `DNS_MAX_CNAMES` | 32 | Максимум CNAME-записей в одном DNS-ответе |
| `recv_buf` | 65 536 | Буфер приёма AF_PACKET (`pkt_capture_t.recv_buf[]`) |
### 1.5 L7-канал и TCP-реассамблеция
| Параметр | Значение | Файл | Назначение |
|----------|----------|------|------------|
| `TCP_REASM_BUCKETS` | 64 | `tcp_reasm.h` | Бакетов в хеш-таблице реассамблеции |
| `TCP_REASM_MAX_ENTRY` | 256 | `tcp_reasm.h` | Жёсткий потолок одновременно буферизуемых соединений (значение `l7TcpReasmMaxEntries` clamp'ится сюда) |
| `TCP_REASM_BUF_SIZE` | 16 КБ | `tcp_reasm.h` | Буфер на одну реассемблируемую запись |
| `NFQ_RECV_BUF_SIZE` | 128 КБ | `nfq_capture.h` | Буфер приёма NFQUEUE (`nfq_capture_t.recv_buf[]`) |
| `NFQ_QUEUE_MAXLEN` | 1024 | `nfq_capture.h` | Максимум пакетов в очереди NFQUEUE на стороне ядра |
| `NFQ_COPY_RANGE` | 0xFFFF | `nfq_capture.h` | Сколько байт пакета копировать из ядра в userspace |
### 1.6 RCI-клиент (`include/rci.h`)
| Параметр | Значение | Назначение |
|----------|----------|------------|
| `RCI_MAX_RESPONSE` | 1 МБ | Размер буферов `raw_buf` и `response_buf` |
| `RCI_TIMEOUT_SEC` | 10 | `SO_RCVTIMEO` / `SO_SNDTIMEO` |
| `POLICY_API_MAX_RETRIES` | 5 | Максимум попыток `GET /rci/show/ip/policy/` |
| `POLICY_API_RETRY_DELAY` | 3 | Секунды между попытками |
> **Жёстких лимитов на число доменов в коде нет** — пул растёт через цепочку чанков по 256 КБ.
> **Длина строки в `domain.conf` и `ip.list` не ограничена** — оба файла читаются через `getline()` (динамический буфер). Прежнего лимита 4096 байт нет.
> Только `hrneo.conf` (`src/config.c`) читается через `fgets` с буфером 4096 байт — но это про **одну строку** `key=value`, что более чем достаточно.
---
## 2. Расход памяти на один домен
В `domain_node_t` через `ht_pool_alloc` (выравнивание по 8):
- `domain_node_t` ≈ **40 байт** (на 64-bit: `char *domain` + `size_t domain_len` + `domain_entry_t{char*, int}` + `struct domain_node *next` = 8 + 8 + 16 + 8)
- Строка домена + `\0`, выровнено по 8: средняя длина 20 → **24 байта**
- Указатель `ipset_name` — переиспользуется через `ipset_name_cache` (политик ≤ 64), отдельная аллокация только для первой записи каждой политики
**Итого ≈ 64–72 байта на домен** в установившемся режиме.
### 2.1 Базовый расход (без watchlist)
| Структура | Размер | Условие |
|-----------|--------|---------|
| Один пустой `pool_chunk_t` | 256 КБ | всегда (создаётся `ht_create()`) |
| `domain_hashtable_t` | ~68 КБ | `buckets[8192]` (64 КБ) + `ipset_name_cache[64][64]` (4 КБ) + `ipset_name_ptrs[64]` (512 байт) |
| `ipset_manager_t` | ~34 КБ | `set_names[512][64]` (32 КБ) + `set_has_timeout[256]` (1 КБ) + `timeout_value[256]` (1 КБ) |
| `rci_client_t` буферы | ~2 МБ | `raw_buf` (1 МБ + 4 КБ) + `response_buf` (1 МБ); выделяются при старте, переиспользуются весь lifetime |
| `nfq_capture_t.recv_buf` | 128 КБ | только при `l7CaptureEnabled=true` |
| `tcp_reasm_t` пул | до 4 МБ | `l7TcpReasmMaxEntries × TCP_REASM_BUF_SIZE` = до 256 × 16 КБ; **при `l7TcpReasmEnabled=false` память не выделяется вообще** |
| `g_all_sorted[]` | ~17 КБ | `unified_target_t[128]` (`MAX_POLICY_ORDER + MAX_INTERFACES`) |
**Итого базовый расход при дефолтном конфиге** (`l7CaptureEnabled=true`, `l7TcpReasmMaxEntries=256`): ≈ **6.5 МБ heap/data**.
При `l7CaptureEnabled=false`: ≈ **2.5 МБ** (без NFQ-буфера и без `tcp_reasm` пула).
---
## 3. Предельные размеры domain.conf
Динамический предел = (свободная RAM) ÷ ~64 байта на домен.
| RAM роутера | Теоретический максимум доменов |
|-------------|--------------------------------|
| 64 МБ (старые) | ~500 тыс. — реально 100–200 тыс. с учётом ядра/сервисов Keenetic и базового расхода hrneo (~6 МБ при L7) |
| 128 МБ | ~1 млн — реально 300–500 тыс. |
| 256 МБ | ~2–4 млн — реально 1–2 млн |
### 3.1 Деградация производительности раньше OOM
- 8192 бакета → при 100 тыс. доменов средняя цепочка ≈ 12 элементов, `match_domain` остаётся `O(1)` на практике
- При 1 млн доменов цепочка ≈ 122 элементов, `match_domain` для домена `a.b.c.d.example.com` делает 5 lookup-ов × проход по цепочке → ощутимый CPU-удар на каждом DNS-пакете
### 3.2 Практический потолок без переписывания
200–500 тыс. доменов на 128 МБ. Выше — нужно увеличивать `DOMAIN_HT_BUCKETS` (рекомендация: `65 536` при > 500 тыс. записей).
### 3.3 Жёсткие ограничения domain.conf
- Не более **64 уникальных политик** **И** не более **64 уникальных интерфейсов** (`MAX_POLICY_ORDER` = 64, `MAX_INTERFACES` = 64 — раздельные счётчики)
- Не более **256 строк с `geosite:TAG/...`** во всём файле (буфер `geosite_rule_t gs_rules[256]` в `main.c`)
- Длина имени политики/интерфейса ≤ **63 байта** (`MAX_POLICY_NAME - 1`)
- Длина одной строки **не ограничена** — читается через `getline()` (динамический буфер); список из тысяч доменов через запятую одной строкой обрабатывается целиком
- Глубина CNAME-цепочки при матчинге ≤ **16** (`MAX_CNAME_CHAIN`); дальше BFS останавливается
---
## 4. Предельные размеры geo.dat (GeoSite)
Загружается в ту же `domain_hashtable_t`, поэтому делит общий бюджет памяти с `domain.conf`.
### 4.1 Per-tag процесс в `build_geosite_domain_map`
1. `extract_geosite_domains` — `malloc` всех доменов одного тега из всех файлов: пиковая аллокация (`capacity` стартует с 4096 и удваивается через `realloc`)
2. `deduplicate_domains` — создаёт временную хеш-таблицу `ht_create()` (минимум один 256 КБ-чанк, растёт по мере вставки)
3. `ht_insert` в основную таблицу
**Пиковая память на тег** ≈ `(N доменов × ~50 байт) × 2` (исходный массив + временная таблица дедупа) + чанки основной таблицы.
| Размер тега | Пиковая RAM |
|-------------|-------------|
| 10 000 доменов | ~1 МБ |
| 100 000 доменов | ~10 МБ |
| 500 000 доменов | ~50 МБ |
| 1 000 000 доменов | ~100 МБ (рискованно на 128 МБ роутере) |
### 4.2 Лимиты geo.dat
- Не более **16 файлов** `GeoSiteFile=` (`MAX_GEO_FILES`)
- Не более **256 правил `geosite:TAG/Policy`** во всём `domain.conf` (`gs_rules[256]`)
- Длина имени тега ≤ **63 байта** (`MAX_TAG_LEN - 1`)
- Размер самого `.dat`-файла **не ограничен** (потоковое чтение через `setvbuf(64 КБ)` + посимвольный `read_varint_stream`)
- Поддерживаются типы доменов **Domain** и **Full**; **Plain** и **Regex** пропускаются с `[WARN]`
### 4.3 Реальный потолок
Для одного тега: 200–500 тыс. доменов. Для всей суммы тегов — общий бюджет watchlist (см. таблицу выше).
---
## 5. Предельные размеры ip.list (CIDR)
### 5.1 Лимиты при автомиграции oversized geoip
Срабатывает фаза 1 в `add_cidr_to_ipsets` при `geoip_count > 0`:
- Не более **16 384 строк** в `ip.list` обрабатывается за одну миграцию (`CIDR_MIGRATE_MAX_LINES`)
- Не более **512 блоков** `/Name` или `#/Name` (`CIDR_MIGRATE_MAX_BLOCKS`)
- Превышение → миграция возвращает `-1` без переписывания файла
### 5.2 Лимиты при обычной загрузке
- Длина строки **не ограничена** (`getline()`)
- Записей на один `ipset` ≤ `IpsetMaxElem` (дефолт `262 144`); при превышении — `[WARN]` и оставшиеся записи пропускаются
- Длина имени блока `/Name` ≤ 63 байта (буфер `cidr_block_t.name[64]`)
- Глубина FNV-1a-индекса `batches[]` / `usage[]` — **256 слотов** (`NAME_INDEX_SLOTS`); при `MAX_POLICY_ORDER * 2 = 128` целях load factor < 50%, коллизии единичные
---
## 6. Лимиты L7-канала
При `l7CaptureEnabled=true`:
- Одновременно буферизуемых TCP-соединений (фрагментированных ClientHello): **`l7TcpReasmMaxEntries`** (default 256, clamp до `TCP_REASM_MAX_ENTRY=256`)
- Размер буфера на одно соединение: **16 КБ** (`TCP_REASM_BUF_SIZE`); если ClientHello превышает 16 КБ → `stat_too_big`, запись освобождается
- TTL неполных записей: **`l7TcpReasmTtlSec`** секунд (default 5)
- Пакетов в очереди NFQUEUE на стороне ядра: **1024** (`NFQ_QUEUE_MAXLEN`); переполнение → `ENOBUFS` → `LOG_WARN`, не fatal
- Окно `connbytes` для `dport 443`: первые **`l7ConnbytesMax`** пакетов после SYN (default 8); для `dport 80` ужимается до `min(N, 4)`
- WAN-интерфейс **один** (`l7WanInterface` или автодетект из `/proc/net/route`)
---
## 7. Бутылочные горлышки в порядке появления
При росте watchlist первым упрётесь в:
1. **`MAX_POLICY_ORDER = 64`** — если в `domain.conf` более 64 разных политик. Жёсткий лимит, лишние молча отбрасываются (`add_unique_name` проверяет `*count < max`).
2. **256 строк `geosite:`** в `domain.conf` — жёсткий лимит (`gs_rules[256]`).
3. **Деградация `match_domain`** при > 100 тыс. доменов (бакетов всего 8192).
4. **Память** — на маленьких роутерах. OOM-killer убьёт процесс без диагностики. Базовый расход + ~2 МБ на буферы RCI + до 4 МБ на L7-реассамблецию + ~64 байта × N доменов.
5. **Стартовое время** — `build_geosite_domain_map` линейно по числу доменов × числу `.dat`-файлов; 500 тыс. × 3 файла ≈ 30–60 секунд парсинга на mipsel-роутере.
---
## 8. Итоговая сводная таблица
| Источник | Теоретический максимум | Практически рекомендуемый максимум |
|----------|------------------------|-------------------------------------|
| **`domain.conf`** (явные домены) | Память ÷ 64 байта | 100–200 тыс. на 128 МБ; 300–500 тыс. на 256 МБ |
| **`geo.dat`** (один тег) | Пиково ≈ 100 байт/домен | 200–500 тыс. доменов на тег |
| **`geo.dat`** (все теги суммарно) | Память ÷ 64 байта | Тот же общий бюджет, что у `domain.conf` |
| **`ip.list`** (при миграции oversized) | 16 384 строки / 512 блоков | Не приближаться к лимиту |
| **Политик** | 64 (`MAX_POLICY_ORDER`) | 64 (жёстко) |
| **Интерфейсов DirectRoute** | 64 (`MAX_INTERFACES`) | 64 (жёстко) |
| **`geosite:` строк в `domain.conf`** | 256 | 256 (жёстко) |
| **`GeoIPFile=` / `GeoSiteFile=`** | 16 каждый (`MAX_GEO_FILES`) | 16 (жёстко) |
| **IP в одном ipset** | `IpsetMaxElem` (default 262 144) | 262 144 (kernel-default) |
| **Кэш существующих ipset** | 512 (`IPSET_MAX_SETS`) | 512 (жёстко) |
| **TCP-реассамблеция (L7)** | 256 одновременных соединений × 16 КБ = 4 МБ | управляется `l7TcpReasmMaxEntries` (1..256) |
| **Глубина CNAME** | 16 шагов BFS (`MAX_CNAME_CHAIN`) | 16 (жёстко) |
| **DNS-ответов в одном пакете** | 128 A/AAAA + 32 CNAME | (жёстко, лишние молча игнорируются) |
---
## 9. Рекомендации по расширению лимитов
Если требуется обслуживать больше доменов:
- **`DOMAIN_HT_BUCKETS`** в `include/hrneo.h` — увеличить до `32 768` или `65 536` (степень двойки, маска `& (DOMAIN_HT_BUCKETS - 1)` работает корректно)
- **`MAX_POLICY_ORDER`** — увеличить если нужно > 64 политик (затронет несколько массивов и `set_has_timeout[256]`-кэш — последний рекомендуется увеличить пропорционально, чтобы load factor оставался разумным)
- **`POOL_CHUNK_SIZE`** — увеличить до 1 МБ для меньшего числа аллокаций при очень большом watchlist
- **`gs_rules[256]`** в `src/main.c` (два места: шаг сбора политик и шаг `build_geosite_domain_map`) — расширить при необходимости > 256 `geosite:`-строк; одновременно увеличить второй параметр в вызове `parse_geosite_rules(..., 256)`
- **`l7TcpReasmMaxEntries`** в конфиге — увеличить (до 256) при большом числе одновременных TLS-соединений с long ClientHello; либо `TCP_REASM_MAX_ENTRY` в `include/tcp_reasm.h`, если 256 мало
- **`IPSET_MAX_SETS`** в `include/ipset_nl.h` — увеличить, если число активных ipset на роутере превышает 512 (актуально только в нестандартной интеграции с другими ipset-пользователями)