# HRNeo — конфигурация и CLI Справочник по параметрам конфигурационного файла `hrneo.conf`, CLI-флагам и формату вспомогательных файлов (`domain.conf`, `ip.list`). **Версия кода:** hrneo 3.11.0-1 --- ## Запуск ```bash hrneo [OPTIONS] ``` Все параметры конфигурационного файла можно передать флагами при запуске. **Приоритет:** `флаги командной строки` > `конфигурационный файл` > `встроенные значения по умолчанию`. Флаги можно указывать частично — недостающие значения берутся из конфигурационного файла или дефолтов. --- ## Служебные флаги | Флаг | Назначение | |------|------------| | `--version`, `-v` | Вывести версию и выйти | | `--help`, `-h` | Вывести справку по флагам и выйти | | `--config ` | Путь к конфигурационному файлу | | `--genconfig [path]` | Создать файл конфигурации со всеми параметрами по умолчанию и выйти | ### `--config ` - по умолчанию: `/opt/etc/HydraRoute/hrneo.conf` - если указан явно и файл недоступен — ошибка и выход - если путь по умолчанию недоступен — продолжение с дефолтами + флаги ### `--genconfig [path]` Поведение по аргументу: - **без аргумента** — `hrneo.conf` рядом с исполняемым файлом (каталог бинарника, не дефолтный путь) - **аргумент-каталог** (или со слешем) — `/hrneo.conf` - **аргумент-файл** — записать ровно по этому пути Пустые multi-value ключи пишутся как `GeoIPFile=`, `GeoSiteFile=`, `PolicyOrder=`, `l7WanInterface=`. > Все параметры имеют встроенное значение по умолчанию (см. колонку «default» в `--help`). Отсутствие любого ключа в конфиге **ИЛИ** среди флагов → используется дефолт (включая строковые пути `watchlistPath` / `CIDRfile` / `logfile` / `log`). Невалидное или пустое значение ключа → `[WARN]` + дефолт. Падения при отсутствии ключей нет. --- ## Флаги параметров Полное зеркало конфига: ``` --autoStart --watchlistPath --clearIPSet --CIDR --CIDRfile --IpsetEnableTimeout --IpsetTimeout --log --logfile --DirectRouteEnabled --InterfaceFwMarkStart --InterfaceTableStart --GlobalRouting --ConntrackFlush --IpsetMaxElem --GeoIPFile # повторяется; при наличии заменяет все GeoIPFile= из конфига --GeoSiteFile # повторяется; при наличии заменяет все GeoSiteFile= из конфига --PolicyOrder --l7CaptureEnabled --l7QueueNum --l7EnableTLS --l7EnableHTTP --l7WanInterface --l7ConnbytesMax --l7TcpReasmEnabled --l7TcpReasmMaxEntries --l7TcpReasmTtlSec ``` --- ## Основные параметры ### `autoStart=true` Разрешить запуск программы. - если `false` — программа немедленно завершается после чтения конфига, `iptables` и `ipset` не трогаются - используется init-скриптом `S99hrneo` для управления запуском без изменения конфига ### `watchlistPath=/opt/etc/HydraRoute/domain.conf` Путь к файлу с доменами и политиками. - формат строки: `domain1,domain2,.suffix,geosite:TAG/ПолитикаИлиИнтерфейс` - любой домен (например, `youtube.com`) → точное совпадение и автоматический суффиксный матч всех его поддоменов - `geosite:TAG` — загружает домены из GeoSite-файла для данного тега - несколько geosite-тегов и обычные домены можно смешивать в одной строке в любом порядке - если цель (после `/`) совпадает с именем сетевого интерфейса — используется DirectRoute, иначе — политика Keenetic - строки, начинающиеся с `#` или `##`, являются комментариями - длина строки не ограничена: список из тысяч доменов через запятую одной строкой обрабатывается целиком (чтение через `getline`; прежнего лимита 4096 байт нет) - DNS-запросы перехватываются у клиентов на интерфейсах любого типа, включая VPN-клиентов роутера (WireGuard / VPN-сервер / IPsec / туннели), а не только LAN ### `clearIPSet=true` Очищать содержимое `ipset` при запуске. - `true` — при старте все `ipset`-множества программы сбрасываются (`flush`), CIDR и DNS-кэш теряются - `false` — сохранять накопленные IP между перезапусками; не совместимо с изменением таймаута `ipset` --- ## CIDR ### `CIDR=true` Включить поддержку статических IP/подсетей. - при включении читается `CIDRfile` и все записи загружаются в `ipset` при старте - одиночный IP с маской `/32` или `/128` добавляется как хост, иначе как подсеть ### `CIDRfile=/opt/etc/HydraRoute/ip.list` Путь к файлу с CIDR-записями. - формат секции: `/ПолитикаИлиИнтерфейс` (заголовок), далее IP/CIDR построчно - `#/ПолитикаИлиИнтерфейс` — отключённая секция, содержимое игнорируется - `##` — комментарий - директива `geoip:CC` внутри секции — загружает CIDR для страны из GeoIP-файлов - пустая строка завершает текущую секцию - длина строки не ограничена (чтение через `getline`; прежнего лимита 4096 байт нет) ### `GeoIPFile=` Путь к файлу базы GeoIP в формате v2ray/xray `.dat`. Параметр повторяемый: ``` GeoIPFile=/opt/etc/x-ui/bin/geoip_RU.dat GeoIPFile=/opt/etc/x-ui/bin/geoip.dat ``` - используется совместно с директивой `geoip:cc` в файле `ip.list` (например `geoip:ru`, `geoip:cn`) - при обработке директивы программа обходит все указанные файлы и собирает CIDR из каждого - файлы читаются потоково без кэширования, в памяти хранятся только извлечённые CIDR-записи - поиск тега в файле выполняется полным сканированием независимо от порядка записей в файле - если параметр не задан, директивы `geoip:` в `ip.list` игнорируются с предупреждением `[WARN]` - если ни один файл не содержит запрошенного кода, выводится предупреждение `[WARN]` - GeoIP-диаппазоны загружаются однократно при старте; `SIGUSR1` их не перечитывает --- ## GeoSite ### `GeoSiteFile=` Путь к файлу базы GeoSite в формате v2ray/xray `.dat`. Параметр повторяемый: ``` GeoSiteFile=/opt/etc/HydraRoute/geosite.dat GeoSiteFile=/opt/etc/x-ui/bin/geosite.dat ``` - используется совместно с директивой `geosite:TAG` в файле `domain.conf` (например `geosite:google`) - при обработке программа обходит все указанные файлы и объединяет домены из каждого - файлы читаются потоково без кэширования, в памяти хранятся только извлечённые домены - поиск тега в файле выполняется полным сканированием независимо от порядка записей в файле - поддерживаются типы доменов: **Domain** (домен + поддомены) и **Full** (только точное имя) - типы **Plain** (keyword-матч) и **Regex** не поддерживаются — пропускаются с предупреждением `[WARN]` - домены автоматически дедуплицируются: если загружены `google.com` и `mail.google.com`, сохраняется только `google.com` (родительский домен покрывает все поддомены) - запись `domain.conf` имеет приоритет над GeoSite: если домен присутствует в обоих источниках, используется политика из `domain.conf` - если параметр не задан, директивы `geosite:` в `domain.conf` игнорируются с предупреждением `[WARN]` - если ни один файл не содержит запрошенного тега, выводится предупреждение `[WARN]` - GeoSite-домены загружаются однократно при старте; `SIGUSR1` их не перечитывает --- ## IPSet ### `IpsetEnableTimeout=true` Включить автоматическое удаление записей по таймауту. - `true` — `ipset`-множества создаются с поддержкой timeout; записи удаляются по истечении `IpsetTimeout` - `false` — записи хранятся бессрочно до перезапуска или ручной очистки - изменение этого параметра требует `clearIPSet=true` при следующем запуске ### `IpsetTimeout=21600` Таймаут записей в секундах (применяется только при `IpsetEnableTimeout=true`). - `21600` = 6 часов, `86400` = 24 часа - при повторном добавлении существующего IP таймаут не обновляется (используется флаг `NLM_F_EXCL`) ### `IpsetMaxElem=262144` Максимальное количество записей в одном `ipset`-множестве. - дефолт `262144` = встроенное ядерное значение; передаётся в `ipset CREATE` как атрибут `IPSET_ATTR_MAXELEM` - значение задаёт жёсткий лимит на каждое `ipset`-множество; IP сверх лимита не добавляются (kernel вернёт `IPSET_ERR_HASH_FULL` → `[WARN]`) - применяется при `CREATE`; изменение лимита требует `clearIPSet=true` и перезапуска - используется как ограничение при загрузке CIDR: если `geoip:TAG` содержит записей больше лимита, тег автоматически отключается (помечается как `#/Too-big-geoip-tag` в `ip.list`, атомарная перезапись через `.tmp + rename`) с `[WARN]` --- ## Логирование ### `log=off` Режим логирования (дефолт `off` — отладочные уровни отключены). | Значение | Поведение | |----------|-----------| | `console` | весь вывод в `stdout` процесса, включая `[DEBUG]`-сообщения | | `file` | запись в файл `logfile`, включая `[DEBUG]`-сообщения | | `syslog` | отправка через `openlog`/`vsyslog` в `LOG_DAEMON` с тегом `"hrneo"`; `logfile` игнорируется | | `off` или иное | отладочные уровни отключены; `[WARN]`/`[ERROR]` продолжают выводиться в `stderr` | **Уровни логов:** - `[DEBUG]` — детали операций - `[INFO]` — штатные события - `[MATCH]` — совпадение домена - `[PROCESSED]` — добавление IP в ipset - `[FILTERED]` — отклонение служебного IP - `[WARN]` — некритичные проблемы - `[ERROR]` — ошибки ### `logfile=/opt/var/log/LOGhrneo.log` Путь к файлу логов. - используется только при `log=file`; при пустом значении логирование отключается - директория создаётся автоматически если не существует - файл открывается в режиме `append`; старые записи не удаляются --- ## Direct Routing ### `DirectRouteEnabled=true` Включить прямую маршрутизацию трафика на сетевые интерфейсы. - при старте программа сканирует `/sys/class/net/` и строит список доступных интерфейсов - если цель в `domain.conf` совпадает с именем интерфейса — настраивается `ip rule + ip route` для данного интерфейса; иначе цель считается политикой Keenetic - если интерфейс DOWN при старте — создаётся `blackhole`-маршрут; при изменении состояния интерфейса маршрут обновляется по сигналу `SIGUSR1` - `false` — все цели в `domain.conf` считаются политиками Keenetic, DirectRoute не настраивается ### `InterfaceFwMarkStart=12289` Начальный `fwmark` для интерфейсов (`0x3001`). - каждому интерфейсу назначается уникальный `fwmark` начиная с этого значения - используется в правилах `iptables CONNMARK` и `ip rule fwmark` ### `InterfaceTableStart=301` Начальный номер таблицы маршрутизации для интерфейсов. - каждому интерфейсу назначается уникальная таблица начиная с этого номера - в таблице создаётся маршрут `default dev <интерфейс>` или `blackhole default` --- ## Conntrack ### `ConntrackFlush=true` Сбрасывать соединения `conntrack` при первом добавлении IP в `ipset`. - `true` (по умолчанию) — при первом добавлении IP в `ipset` (`NLM_F_EXCL` вернул `err=0`) hrneo делает netlink-DUMP `conntrack`-таблицы и отправляет `DELETE` для каждой записи, у которой dst-IP совпадает с только что добавленным. Это нужно, чтобы уже установленное соединение к этому IP получило новый `fwmark` при следующем пакете и ушло через нужный туннель/интерфейс - фактический сброс происходит **ТОЛЬКО** при одновременном выполнении трёх условий: 1. `ConntrackFlush=true` 2. IP добавлен в `ipset` впервые (повторное добавление того же IP no-op — `NLM_F_EXCL` → `IPSET_ERR_EXIST`) 3. существует активная `conntrack`-запись с `dst==этот IP` Если соединения к IP ещё нет — DUMP проходит вхолостую, DELETE не отправляется. - `false` — не выполнять `conntrack flush`; соединения, установленные ДО добавления IP в `ipset`, продолжат идти с прежним `fwmark` до переподключения - `fork/exec` не используется — всё через прямой netlink-сокет (`NETLINK_NETFILTER`, long-lived, открыт один раз при старте) --- ## Порядок правил и приоритет политик ### `PolicyOrder=HydraRoute,RU,CN` Единый управляемый приоритет политик/интерфейсов hrneo. Решает коллизии маршрутизации, когда один и тот же IP или один и тот же домен попадает сразу в несколько целей (политик и/или DirectRoute-интерфейсов) watchlist'а. #### Синтаксис - значение — список имён через запятую (имена политик Keenetic и имена сетевых интерфейсов одинаково; hrneo их различает автоматически через `drm_classify_target` по `/sys/class/net`) - пробелы вокруг запятых обрезаются (`trim_whitespace`) - до `MAX_POLICY_ORDER=64` элементов - если параметр отсутствует или пустой — все цели сортируются строго алфавитно #### Правила сортировки (`sort_policies`) - цели из `PolicyOrder` выстраиваются в указанном порядке первыми - остальные цели (есть в watchlist/CIDRfile/geosite, но нет в `PolicyOrder`) добавляются после них алфавитно (`qsort`) - цели из `PolicyOrder`, отсутствующие во всех источниках (watchlist + CIDRfile + geosite-правила) — пропускаются с предупреждением: ``` [WARN] PolicyOrder: policy 'X' not found, skipping ``` - полный порядок печатается в лог при старте: ``` [INFO] Target order (N): [0] HydraRoute (policy) [1] nwg0 (interface, fwmark=0xNNNN) ... ``` #### Где порядок реально применяется (ДВА независимых уровня) **1) Порядок правил `iptables/mangle/PREROUTING`** hrneo проходит по итоговому отсортированному массиву `g_all_sorted[]` и добавляет правила `CONNMARK` по одной паре на цель — в порядке массива. `iptables-restore --noflush` сохраняет этот порядок при apply. Поскольку iptables проверяет правила сверху вниз и берёт **первое** совпадение, при попадании пакета в несколько `ipset` одновременно (например, IP сразу в `HydraRoute` и в `RU`) победит та цель, чьё правило добавлено раньше — то есть стоящая раньше в `PolicyOrder`. **2) Выбор политики при матчинге домена** При резолве DNS-ответа или L7-имени hrneo ищет домен в хеш-таблице и обходит все суффиксы; если один домен присутствует в нескольких целях (например, в watchlist прописано): ``` google.com/HydraRoute # родитель mail.google.com/RU # поддомен в другой политике ``` или CNAME-цепочка проходит через домены разных политик — выбор делается по двум критериям: - `priority` = индекс цели в `PolicyOrder` (отсутствующие = `order_count`, то есть «последние») - `specificity` = длина совпавшего суффикса (точное совпадение = `len+1`, выше всех) Меньший `priority` побеждает; при равных `priority` выбирается более специфичный (более длинный) суффикс. То есть `PolicyOrder` влияет и на **выбор** ipset для конкретного DNS-ответа, а не только на порядок правил. #### Практически Положите в начало `PolicyOrder` те политики, в которые надо направлять трафик при коллизиях (например, узкие/более специфичные туннели), а оставшиеся (общие, fallback) — в конце или вне списка. #### Обновление при SIGUSR1 - `SIGUSR1` **НЕ** перечитывает `hrneo.conf` и сам `PolicyOrder` из памяти; но `apply_unified_connmark_rules` вызывается заново и пересоздаёт правила в текущем порядке `g_all_sorted[]` (то есть пересборка iptables идёт в том же порядке, что и при старте — это снимает рассинхронизацию правил с актуальными mark'ами политик, полученными от RCI) - для применения нового `PolicyOrder` нужен перезапуск (`neo restart`) --- ## Глобальная маршрутизация ### `GlobalRouting=false` Режим обработки политик роутера. - `false` (по умолчанию) — уважать существующие политики роутера (`NoVPN`, `Policy0` и т.д.); правила HydraRoute/интерфейсов применяются только к пакетам без установленной политики; устройства с назначенными политиками роутера сохраняют свою маршрутизацию - `true` — перезаписывать все политики роутера; правила применяются ко всем пакетам независимо от их политик роутера --- ## Взаимодействие с роутером через RCI **RCI** (Remote Configuration Interface) Keenetic. hrneo **НЕ** вызывает системные утилиты Keenetic (`ndmc` / `ndmq` / `curl` / `wget` / `jq` / `python`) для общения с роутером. Вместо этого внутри бинаря реализован собственный минимальный HTTP/JSON-клиент к локальному эндпойнту `ndmsv` (TCP `127.0.0.1:79`). Это значит: - нулевые runtime-зависимости от userspace утилит роутера - никаких `fork`/`exec` на каждое обращение → меньше latency и пиков CPU - статическая сборка остаётся самодостаточной; конфигурация роутера читается и изменяется напрямую через netlink-подобный по простоте JSON API - параметра конфига для RCI нет — endpoint захардкожен (`localhost:79` — стандартный порт RCI на всех современных прошивках Keenetic v3+) ### Что hrneo делает через RCI **1. Создание отсутствующих политик доступа (при старте)** Для всех уникальных политик из watchlist + CIDRfile + geosite-правил hrneo формирует JSON-массив команд: ```http POST /rci/ HTTP/1.0 Content-Type: application/json [{"parse":"ip policy HydraRoute"}, {"parse":"ip policy RU"}, {"parse":"ip policy CN"}] ``` - если политика уже существует — команда no-op на стороне роутера - если политики нет — создаётся пустая (без VPN-интерфейсов; их присваивает администратор вручную через веб-интерфейс Keenetic → Приоритеты подключений → Политики доступа или через HRWeb) - После — `POST /rci/` с `{"system":{"configuration":{"save":true}}}` (эквивалент `system configuration save` — записывает изменения в startup-config роутера) - Лог: `[INFO] Policy creation commands executed` - Имена политик с интерфейсами не смешиваются: цели, классифицированные `drm_classify_target` как сетевые интерфейсы (DirectRoute), в RCI не отправляются — для них правила маршрутизации настраиваются через `ip rule + ip route`, а не через политики Keenetic **2. Получение `markID` каждой политики (при старте и по SIGUSR1)** `GET /rci/show/ip/policy/` возвращает JSON вида: ```json {"HydraRoute":{"mark":"0x21", ...}, "RU":{"mark":"0x22", ...}, ...} ``` - hrneo вручную парсит ответ: проход по парам `{policy_name: {... "mark": "0xNN" ...}}` через подсчёт фигурных скобок с учётом строковых литералов (`find_matching_brace`), внутри каждого объекта ищет ключ `"mark"` через `strstr`, извлекает hex-значение и снимает префикс `0x` (хранится как строка для прямой подстановки в `iptables --set-xmark 0xNN`) - Лог при `log=console/file`: `[DEBUG] RCI policy: HydraRoute mark=0x21` - `markID` используется в правилах `CONNMARK` для связи `ipset` → политика **3. Retry-логика markID** - `rci_get_policies_with_retry`: до `POLICY_API_MAX_RETRIES=5` попыток с интервалом `POLICY_API_RETRY_DELAY=3` секунды между попытками - В `apply_unified_connmark_rules` дополнительный внутренний цикл (до 5 попыток, sleep 4s): если хотя бы у одной политики `mark` пустой — повтор всего блока (роутер может не сразу присвоить `markID` свежесозданной политике) - Если после всех попыток у политики нет `mark` — `LOG_WARN "Policy %s has no mark ID, skipping"`, цель пропускается (правила `CONNMARK` для неё не создаются; `ipset` продолжает заполняться, но трафик не маркируется) **4. Сетевые параметры** - TCP к `127.0.0.1:79` (`INADDR_LOOPBACK`), `SO_RCVTIMEO`/`SO_SNDTIMEO` = `RCI_TIMEOUT_SEC=10` секунд (для send и recv отдельно) - HTTP/1.0 (`Connection: close` по умолчанию — каждый запрос = новый TCP-сокет, ответ читается до EOF) - Заголовки формируются вручную через `snprintf` - Ответ читается в буфер `RCI_MAX_RESPONSE+4096` (~1 МБ + headroom), парсинг тела — `strstr("\r\n\r\n")` + проверка `"HTTP/"` и кода 200 - Буферы `raw_buf` (~1 МБ + 4 КБ) и `response_buf` (~1 МБ) выделяются один раз через `rci_client_init` и переиспользуются всё время жизни демона (никаких `malloc` на hot path при `SIGUSR1`) **5. JSON-парсер** - самописный, без внешних библиотек (cJSON / json-c / jansson не подключены) - `find_matching_brace`: подсчёт `{`/`}` с учётом строковых литералов (двойные кавычки, экранирование `\"`) - извлечение полей `"mark"` через простые `strstr` — формат ответа RCI Keenetic стабилен и компактен, полноценный JSON-парсер не нужен - размер: ~250 строк всего файла `src/rci.c` ### Системные требования - демон запущен от root (доступ к `loopback:79` + локальной аутентификации NDM, которая для root прозрачна) - RCI на роутере включён (на всех современных прошивках Keenetic — да, по умолчанию слушает `127.0.0.1:79` для управления через веб-интерфейс и системные утилиты) --- ## Сигналы управления ### `SIGUSR1` Обновить правила `iptables` и состояние интерфейсов без перезапуска. - пересоздаёт `CONNMARK`-правила в `mangle/PREROUTING` на текущей конфигурации в памяти (config, watchlist и GeoSite не перечитываются) - обновляет состояние интерфейсов (`up`/`down`) для DirectRoute и корректирует маршруты - если `l7CaptureEnabled=true` и L7-перехват успешно стартовал — заново ставит `NFQUEUE`-правила для портов 80/443 на WAN-интерфейс (idempotent через `iptables -C`); при `l7CaptureEnabled=false` шаг пропускается - **debounce:** повторный `SIGUSR1` во время обработки предыдущего откладывается на 5 секунд - отправляется автоматически хуком `/opt/etc/ndm/netfilter.d/015-hrneo.sh` при изменении `mangle`-таблицы роутером Keenetic ### `SIGTERM` / `SIGINT` Штатная остановка: - удаляются `iptables`/`ip6tables` `CONNMARK`-правила - снимаются L7 `NFQUEUE`-правила (если были установлены) - удаляются `ip rule` и `ip route` для DirectRoute-интерфейсов - закрывается `ipset` netlink-сокет - удаляется PID-файл --- ## L7-перехват HTTP/HTTPS ### `l7CaptureEnabled=true` Главный выключатель L7-механизма (TLS SNI + HTTP Host). - `false` — поведение идентично релизам без L7: DNS-only мониторинг, `NFQUEUE`-правила не ставятся, netlink-сокет очереди не открывается, kernel-модуль `xt_connbytes` не загружается, дополнительной CPU-нагрузки нет - `true` (по умолчанию) — параллельно с DNS-каналом hrneo: 1. резолвит WAN-интерфейс (см. `l7WanInterface`) 2. загружает kernel-модуль `xt_connbytes` через syscall `init_module` (если ещё не загружен; путь `/lib/modules/$(uname -r)/xt_connbytes.ko`) 3. открывает `NFQUEUE` через `NETLINK_NETFILTER` на `l7QueueNum` 4. добавляет в `iptables`/`ip6tables` `mangle/POSTROUTING` правила пересылки первых 2..N пакетов TCP-потока (`dport 443` и `80`) в очередь `NFQUEUE` 5. на каждый пакет парсит TLS ClientHello (SNI) либо HTTP Host 6. при совпадении имени с watchlist — destination-IP попадает в соответствующий `ipset` так же, как DNS-ответ; дальше обычная цепочка `CONNMARK`/маршрутизации - закрывает «слепые зоны» DNS-only схемы: клиенты с DoH/DoT/DoQ, hardcoded-IP с TLS SNI, легаси-HTTP - расшифровка DoH/DoT/DoQ/ECH невозможна (требует MITM) и не делается - горячее переключение не поддерживается; смена флага требует `/opt/etc/init.d/S99hrneo restart` ### `l7QueueNum=210` Номер `NFQUEUE` для L7-перехвата. - пространство номеров глобальное (netfilter); при конфликте с zapret/nfqws/tpws задать любое непересекающееся (диапазон `0..65535`) ### `l7EnableTLS=true` Парсить TLS ClientHello на `dport 443`. - `false` — пакеты порта 443 проходят через диспетчер без вызова `tls_extract_sni` и без матчинга по watchlist; правило firewall при этом всё равно установлено (трафик проходит через очередь и сразу `ACCEPT`-ится) - имеет эффект только при `l7CaptureEnabled=true` ### `l7EnableHTTP=true` Парсить HTTP Host на `dport 80`. - `false` — пакеты порта 80 не анализируются (см. `l7EnableTLS`) - имеет эффект только при `l7CaptureEnabled=true` ### `l7WanInterface=` Имя WAN-интерфейса для firewall-правил L7. - пусто (по умолчанию) — hrneo определяет автоматически по таблице маршрутизации: читает `/proc/net/route`, ищет строку с `Destination=00000000` (маршрут по умолчанию) и берёт имя интерфейса из колонки 0 - явное значение (например, `l7WanInterface=eth3`) — отключает автодетект и подставляется в правила как есть - если интерфейс невозможно определить (ни конфиг, ни автодетект) — L7-перехват отключается с предупреждением `[WARN]`, DNS-канал продолжает работать ### `l7ConnbytesMax=8` Верхняя граница окна `connbytes` для правила `dport 443`. - правило: ``` -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 2:N ``` - нижняя граница фиксирована (`2`) — пропускаем SYN, ловим со второго пакета - для `dport 80` значение автоматически ужимается до `min(l7ConnbytesMax, 4)` — HTTP-запрос укладывается в первые пакеты, большее окно избыточно - увеличение помогает при сильно фрагментированном TLS ClientHello (Kyber/MLKEM) --- ## TCP-реассамблеция длинных ClientHello Современные браузеры Chrome/Firefox при включённом post-quantum keyshare (X25519MLKEM768) отправляют ClientHello размером ~3.5 KB. TCP MSS обычно 1460 байт, поэтому ClientHello разрезается на 2–3 TCP-сегмента. SNI extension в первом сегменте обычно отсутствует — для его извлечения hrneo буферизует фрагменты в памяти до сборки полного TLS record'а. ### `l7TcpReasmEnabled=true` Включить буферизацию неполных ClientHello. - `true` (по умолчанию) — для каждого TLS-соединения, у которого в первом полученном пакете `record_len > payload_len`, создаётся запись в хеш-таблице по 5-tuple (`saddr:sport:daddr:dport:family`). Последующие пакеты с правильным следующим TCP `seq` дописываются; при достижении `record_len` запускается `tls_extract_sni` на собранном буфере, и при совпадении с watchlist destination-IP попадает в `ipset` с тегом `[TLS-SNI]` - `false` — длинные ClientHello (Kyber/MLKEM) игнорируются; fast-path для коротких CH (помещающихся в один TCP-сегмент) продолжает работать. Можно использовать для regression-сравнения или экономии памяти на маломощных роутерах - имеет эффект только при `l7CaptureEnabled=true` и `l7EnableTLS=true` - **out-of-order TCP-сегменты** (`next seq > expected`): запись удаляется, счётчик `stat_drop_gap`. На стабильных каналах client→server такое практически не встречается; out-of-order reasm с heap'ом отложен в подфазу 2.1 - **ретрансмиссии** (`seq < expected`): тихо игнорируются, счётчик `stat_retransmit` ### `l7TcpReasmMaxEntries=256` Максимум одновременно буферизуемых соединений. - допустимый диапазон `1..256` (`TCP_REASM_MAX_ENTRY`) - память: ~16 KB на запись (`TCP_REASM_BUF_SIZE`) + ~80 байт overhead. При `max=256` hrneo занимает дополнительно ~4 MB heap. При `max=64` — 1 MB - при достижении лимита срабатывает LRU-eviction: удаляется самая старая запись по `ts_added`, счётчик `stat_evicted`. Под нормальной нагрузкой (50–200 conn/s) eviction не должен происходить — это индикатор настройки слишком маленького `max` или нагрузки выше ожидаемой - установка в `0` или отсутствие параметра — используется значение по умолчанию `256` ### `l7TcpReasmTtlSec=5` TTL неполных записей в секундах. - раз в секунду срабатывает `timerfd`, hrneo сканирует все buckets и удаляет записи старше TTL (счётчик `stat_expired`) - 5 сек с запасом покрывают типичный TLS handshake; больше не нужно, т.к. правило firewall пропускает только первые 2..8 пакетов flow - при значимом `stat_expired` стоит проверить health сети (RTT, потери); в норме значение должно быть около нуля --- ## Системные требования для `l7CaptureEnabled=true` - `iptables`/`ip6tables` с поддержкой матчей `connbytes`, `length`, `tcp` + target `NFQUEUE` - kernel-модули `xt_NFQUEUE`, `nfnetlink_queue` (обычно уже загружены) - kernel-модуль `xt_connbytes` — на Keenetic присутствует в `/lib/modules`, но не автозагружается; hrneo подгружает сам через syscall `init_module` - демон запущен от root (доступ к `NETLINK_NETFILTER`, `AF_PACKET`, `init_module`) ### Лог-сообщения L7 при старте ``` [INFO] L7 WAN interface: eth3 [INFO] kmod xt_connbytes loaded [INFO] L7 firewall rules installed (new=4, already present=0, wan=eth3) [INFO] L7 capture enabled, NFQ #210 (TLS=1 HTTP=1) [WARN] L7 capture: WAN interface unknown; disabling L7 [WARN] L7 firewall install failed; closing NFQ [MATCH] [TLS-SNI] target.example -> HydraRoute [PROCESSED] [TLS-SNI] target.example -> 1.2.3.4 [HydraRoute] [MATCH] [HTTP-Host] legacy.example -> Other [MATCH] [DNS] target.example -> HydraRoute ``` > Теги `[DNS]` / `[TLS-SNI]` / `[HTTP-Host]` различают источник во всех логах. --- ## Примеры использования GeoSite в `domain.conf` ``` ## Одна категория GeoSite geosite:google/HydraRoute ## Несколько категорий GeoSite в одной строке geosite:google,geosite:netflix/HydraRoute ## GeoSite и обычные домены вместе, порядок не важен geosite:google,youtube.com,youtu.be/HydraRoute youtu.be,youtube.com,geosite:netflix/HydraRoute ## Обычные домены без GeoSite youtube.com,youtu.be/HydraRoute ## Прямая маршрутизация на интерфейс youtube.com/nwg0 ## Блокировка через отдельную политику geosite:CATEGORY-ADS-ALL/Block ``` ### Лог-сообщения GeoSite при старте ``` [DEBUG] geosite:google: M entries total, K Domain/Full before dedup, L after dedup [WARN] geosite:google: X Plain-type entries skipped [WARN] geosite:google: Y Regex-type entries skipped (not implemented) [WARN] GeoSite total: X Plain and Y Regex entries skipped ``` --- ## Популярные категории GeoSite/GeoIP (примеры) | Тег | Содержимое | |-----|------------| | `google` | Google, YouTube и связанные сервисы | | `netflix` | Netflix и CDN | | `facebook` | Facebook, Instagram, WhatsApp | | `twitter` | Twitter, TikTok | | `telegram` | Telegram и зеркала | | `category-tracker-all` | трекеры и аналитика (для блокировки) | | `ru` | российские сервисы (Yandex, VK, Mail.ru и т.д.) | | `us` | американские сервисы | > Полный список доступных категорий зависит от используемого файла `geosite.dat`.