# Config Parser (config_parser.c/h) ## 1. Назначение INI-style парсер конфигурационного файла uTun. Читает текстовый конфиг, разбирает его на структуры и возвращает заполненный `struct utun_config` — корневое представление всех настроек. Поддерживает два режима входа: - `parse_config(filename)` — чтение из файла - `parse_config_from_buf(buf, len, filename)` — чтение из буфера (для chatgui/in-memory использования) Формат: стандартные `.ini`-секции `[section]`, строки `key=value`, комментарии `#`. Именованные секции через двоеточие: `[server:main]`, `[client:peer1]`, `[network:mynet]`. Файл и буфер проходят один разбор. Общие функции в `config_ini.h` удаляют пробелы вокруг имён секций, ключей и значений. Имена секций нечувствительны к регистру, имена ключей чувствительны. Встроенный NUL и физические строки длиннее 1022 байтов до LF запрещены; CR учитывается в этом пределе. Ошибки разбора опций `[nat]` отклоняют весь конфиг. Формат статического проброса: `forward=proto:node_id_hex:IPv4:internal_port:external_port`; узел назначения обязателен, оба порта — десятичные 1..65535. Динамический диапазон дополнительно проверяется при запуске NAT. ## 2. Как пользоваться ```c struct utun_config *cfg = parse_config("/etc/utun/config.ini"); if (!cfg) { /* ошибка — смотри логи DEBUG_CATEGORY_CONFIG */ } // Работа с конфигом... const char *tun_name = cfg->global.tun_ifname; struct CFG_SERVER *srv = cfg->servers; while (srv) { ip_str_t addr = sockaddr_storage_to_str(&srv->ip); srv = srv->next; } // Освобождение free_config(cfg); ``` **Нюансы:** - `parse_config` возвращает `NULL` при ошибках открытия, чтения, закрытия, памяти и обязательных проверок формата. Некоторые ошибки отдельных опций только логируются и пропускаются; ненулевой результат не гарантирует корректность всех полей. - Обработка длинных значений зависит от поля: часть строк обрезается, `assign_string` отклоняет значение без изменения поля. - Ключи клиента `link` требуют, чтобы сервер (`[server:имя]`) был объявлен выше по файлу. Иначе ошибка. - `tun_enabled` по умолчанию `0`. При явном включении и отсутствии имени интерфейса используется `tun0`. - IPv6-адреса в `[server:...] addr` поддерживают префиксы `temporary_ipv6:` и `permanent_ipv6:`. - HEX декодируется после проверки каждого символа. Ошибочный список REALITY `short_ids` отклоняется целиком и оставляет `short_id_count=0`; ошибочный последний REALITY-ключ сбрасывает соответствующий `has_*_key`. ## 3. API ### Структуры | Структура | Назначение | |-----------|------------| | `struct utun_config` | Корневой контейнер: `global` + связные списки `servers`, `clients`, `route_subnets`, `my_subnets`, `networks` | | `struct global_config` | Глобальные настройки (ключи, TUN, таймауты, firewall, NAT, SOCKS/HTTP-прокси, NTP, debug, allowed_keys...) | | `struct CFG_SERVER` | Локальный серверный линк: `addr` (ip:port), `netif_index`, `so_mark`, `fib`, `type`, `transport` (udp/tcp), `mtu`, `only_local` | | `struct CFG_CLIENT` | Удалённый пир: `peer_public_key_hex`, `keepalive`, связный список `links` | | `struct CFG_CLIENT_LINK` | Привязка клиента к локальному серверу: `local_srv` (указатель на `CFG_SERVER`), `remote_addr` | | `struct CFG_ROUTE_ENTRY` | Маршрут: `IP` + `netmask` | | `struct CFG_NETWORK` | Именованная сеть: `id` (56-bit), `pubkey_hex`, `signing_key_hex` | | `struct CFG_FIREWALL_RULE` | Правило фаервола: `ip` (host order), `port`, `bypass` | | `struct CFG_ALLOWED_KEY` | Разрешённый публичный ключ (32 байта binary) для контрольного сервера | | `struct CFG_CONTROL_ALLOW` | Разрешённая подсеть для control-сервера | | `struct CFG_NTP_SERVER` | NTP-сервер: имя хоста | ### Секции конфига | Секция | Обработчик | Ключевые поля | |--------|-----------|---------------| | `[global]` | `parse_global()` | `my_node_name`, `my_private_key`, `my_public_key`, `my_node_id`, `tun_ifname`, `tun_ip`, `mtu`, `keepalive_*`, `bbr_max_cwnd`, `debug_level`, `log_file`, `db_path`, `db_sync_*`, `enable_timestamp`, `enable_colors`, `tun_test_mode` | | `[server:name]` | `parse_server()` | `addr`, `so_mark`, `fib`, `netif`, `type` (public/nat/private/local), `transport` (udp/tcp), `mtu`, `only_local` | | `[client:name]` | `parse_client()` | `link` (формат: `server_name:ip:port`), `peer_public_key`, `keepalive` | | `[network:name]` | `parse_network()` | `id` (hex), `pubkey`, `signing_key` | | `[routing]` | inline | `route_subnet`, `my_subnet` | | `[firewall]` | `parse_firewall_rule()` | `allow=IPv4[:port]` или `allow=all`; порт 0–65535, 0 разрешает весь IP; CIDR запрещён | | `[control]` | `parse_control()` | `ip`, `port`, `allow` (подсеть) | | `[allowed_keys]` | inline | `allow_all`, `key` (64 hex) | | `[nat]` | `parse_nat()` | `tun_ifname`, `tun_ip`, `nat_via`, `port_start`, `port_end`, `forward` (`proto:node_id_hex:IPv4:in_port:ext_port`) | | `[tcp_proxy_client]` | `parse_tcp_proxy_client()` | `enabled`, `tun_name`, `tun_ip`, `mtu`, `via_node`, `socks_*`, `http_proxy_*`, `forward` (port -> ip:port) | | `[tcp_proxy_server]` | inline | `tcp_recv_buf` | | `[ntp]` | inline | `enabled`, `server`, `interval` | | `[stun]` | `parse_stun()` | `enabled`, повторяющийся `server=host[:port]`; минимум два сервера при включении | | `[gui]` | игнорируется | (зарезервировано) | ### Функции | Функция | Назначение | |---------|------------| | `parse_config(filename)` | Читает и закрывает файл, затем вызывает `parse_config_from_buf`. Возвращает `utun_config*` или `NULL`. | | `parse_config_from_buf(buf, len, filename)` | Парсит конфиг из буфера (in-memory). `filename` используется только для сообщений об ошибках. | | `free_config(config)` | Рекурсивно освобождает все связные списки и сам `utun_config`. Безопасен для `NULL`. | | `print_config(config)` | Выводит всё содержимое конфига через `DEBUG_INFO(DEBUG_CATEGORY_CONFIG, ...)`. Полезно для проверки после парсинга. | | `update_config_keys(filename, priv_key, pub_key)` | **Устарела.** Дописывает ключи в конец файла. Используй `config_ensure_keys_and_node_id()` из `config_updater.h`. | ### Внутренние хелперы (static) | Функция | Назначение | |---------|------------| | `config_ini_trim(str)` | Общий INI-код: удаляет пробелы слева и справа на месте, сохраняя адрес строки | | `config_ini_key_value(line, ...)` | Общий INI-код: разбирает `key=value`, удаляет комментарий после `#` и пробелы | | `parse_ip_with_netmask(str, ip, netmask)` | Парсит `1.2.3.4/24` или `::1/64` в `struct IP` | | `parse_sockaddr(addr, port, sockaddr)` | Преобразует адрес+порт в `sockaddr_storage` через `getaddrinfo` | | `parse_address_and_port(str, sockaddr)` | Разбирает строку `ip:port` → вызов `parse_sockaddr` | | `get_netif_index(ifname)` | Получает индекс сетевого интерфейса через `if_nametoindex` | | `parse_firewall_rule(rule_str, global)` | Строго разбирает `allow=IPv4[:port]` или `all`; ошибка/неизвестная опция firewall отменяет весь новый конфиг | | `parse_control_allow(rule_str, global)` | Парсит `allow=IP[/CIDR]` | | `config_ini_hex(hex_str, binary, len)` | Общий INI-код: проверяет весь HEX до декодирования |