Document config identity and publication guarantees

This commit is contained in:
evgeny committed 2026-10-21 19:04:53 +02:00
1 parent bb87a0845b
commit 80eb331d6a
2 files changed
+79 -53

No files matched your search

+12 -3
View File
@@ -1,6 +1,10 @@
/* INI-конфигурация ядра: слушающие сокеты, исходящие пиры, сети/подсети и настройки сервисов.
* parse_config читает файл, parse_config_from_buf — текст; filename задаёт источник в диагностике.
* Успех возвращает выделенную utun_config со списками; освобождение — только free_config.
* Файл и буфер проходят один разбор. Пробелы вокруг секций, ключей и значений удаляются;
* имена секций нечувствительны к регистру, имена ключей чувствительны. Комментарий — #.
* HEX проверяется целиком; ошибочный список REALITY short_ids не применяется частично.
* Встроенный NUL и строки >1022 байтов до LF запрещены (CR входит в этот предел).
* Парсер заполняет модель, запуск сетевых ресурсов выполняет utun_instance. */
#ifndef CONFIG_PARSER_H
#define CONFIG_PARSER_H
@@ -133,7 +137,7 @@ struct global_config {
char my_public_key_hex[MAX_KEY_LEN];
uint64_t my_node_id;
uint8_t client_type; // 0=server, 1=desktop, 2=mobile
int tun_enabled; // 1 = create TUN (default), 0 = skip TUN (run without root)
int tun_enabled; // 1 = запрошен TUN, 0 = выключен (значение по умолчанию)
char tun_ifname[16]; // TUN interface name (e.g., "tun12")
struct IP tun_ip;
int mtu;
@@ -141,7 +145,7 @@ struct global_config {
char control_ip[MAX_ADDR_LEN]; // Control server IP address
// Debug and logging configuration
char log_file[256]; // Path to log file (empty = stdout)
char db_path[256]; // Path to SQLite database (empty = disabled)
char db_path[256]; // Каталог chats.db; пусто — ядро использует SQLite :memory:
int db_sync_enabled; // 1 = enable distributed DB sync (default: 0)
int chatserver_enabled; // 1 = chat server mode (set by [chatserver] section presence)
uint32_t db_sync_ttl; // TTL for unsent records in seconds (default: 86400)
@@ -165,7 +169,7 @@ struct global_config {
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
int keepalive_interval; // keepalive interval in ms (default: 200)
int keepalive_adaptive; // 1 = adaptive period (default), 0 = fixed interval
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 1048576)
// Firewall configuration
struct CFG_FIREWALL_RULE *firewall_rules;
@@ -261,7 +265,12 @@ struct utun_config {
struct CFG_NETWORK* networks;
};
/* Непустой путь; результат освобождать free_config. NULL при ошибке открытия, чтения,
* закрытия, памяти или обязательной проверки формата. Часть ошибочных опций только
* логируется и пропускается; ненулевой результат не гарантирует корректность всех полей. */
struct utun_config* parse_config(const char *filename);
/* buf — len байтов без завершающего NUL (NULL допустим при len=0), буфер не меняется.
* filename — источник для диагностики, NULL означает <buffer>. Возврат как у parse_config. */
struct utun_config* parse_config_from_buf(const char *buf, size_t len, const char *filename);
void free_config(struct utun_config *config);
void print_config(const struct utun_config *config);
+67 -50
View File
@@ -1,67 +1,84 @@
# Config Updater (config_updater.c/h)
## 1. Назначение
Модуль обеспечивает согласованные X25519-ключи и node_id в INI-файле до его загрузки ядром.
При включённом REALITY также обеспечивает private_key и непустой список short_ids.
Публичный контракт приведён в config_updater.h, общий разбор строк — в config_ini.h.
Утилита автоматической инициализации и обновления ключей в конфигурационном файле uTun.
## Выбор значений
Основная задача — гарантировать, что в конфиге всегда присутствуют корректные и согласованные:
- `my_private_key` (X25519, 64 hex символа)
- `my_public_key` (производный от private, 64 hex)
- `my_node_id` (SHA256-идентификатор узла, 16 hex символов, вычисляется из private key)
Имена секций сравниваются целиком без учёта регистра, пробелы вокруг имени секции,
ключа и значения удаляются. Имена ключей чувствительны к регистру. Комментарии — #.
Последнее текстовое значение ключа во всех соответствующих секциях определяет результат,
включая пустое или некорректное. Опции из других секций не участвуют.
Если чего-то нет или значение неконсистентно — модуль генерирует недостающее через `secure_channel.h` и атомарно перезаписывает конфиг, сохраняя всё остальное содержимое файла нетронутым.
- Корректный my_private_key — ровно 64 HEX-символа; его бинарное значение сохраняется.
- my_public_key вычисляется через sc_compute_public_key_from_private().
- my_node_id вычисляется через sc_derive_node_id_from_pubkey(): SHA256 публичного ключа,
первые восемь байтов в представлении функции, старший бит сброшен.
- Если приватный ключ отсутствует или некорректен, создаётся новая идентичность.
- Сравнение HEX выполняется по байтам: регистр букв не вызывает перезапись.
- При включённом REALITY некорректный/отсутствующий private_key генерируется заново.
Некорректный/пустой/отсутствующий short_ids заменяется четырьмя случайными ID.
Корректный список сохраняется целиком. Ошибка одного элемента отклоняет весь список.
- При выключенном REALITY его параметры не исправляются.
## 2. Как пользоваться
HEX проверяется посимвольно до декодирования. Частичные результаты проверки не применяются.
Встроенный NUL и физические строки длиннее 1022 байтов до LF запрещены; CR учитывается в этом пределе.
```c
#include "config_updater.h"
## Изменение текста
int ret = config_ensure_keys_and_node_id("/etc/utun/config.ini");
if (ret == 0) {
// Конфиг готов: ключи и node_id гарантированно на месте
} else {
// Ошибка — смотри DEBUG_*(DEBUG_CATEGORY_CONFIG)
}
```
Исправный файл остаётся побайтно неизменным. Если поле требует исправления,
обновляются все его вхождения в соответствующих секциях. Недостающие поля добавляются
в конец последней подходящей секции; отсутствующая секция создаётся в конце файла.
**Типовой сценарий (первый запуск):**
1. Файла нет → создаётся минимальный `[global]` с `my_node_name`, сгенерированными ключами и `my_node_id`.
2. Файл есть, но нет `my_private_key` → генерируется новая X25519-пара, записываются все три поля.
3. Файл есть с `my_private_key`, но нет `my_public_key` или `my_node_id` → вычисляются из private key, дописываются.
4. Все поля есть, но `my_public_key` не соответствует `my_private_key` или `my_node_id` не совпадает → перезаписываются на корректные.
Посторонние строки сохраняются побайтно. В изменяемых строках сохраняются пробелы,
комментарии и окончания строк вокруг заменяемого значения. Добавляемые строки используют
тип первого окончания строки в файле, либо LF. При необходимости добавляется разделитель
после последней строки без LF. Новый или пустой файл получает [global] с тремя полями идентичности.
**Нюансы:**
- Вычисление pubkey: `sc_compute_public_key_from_private(priv_bin, pub_bin)`.
- Вычисление node_id: `sc_derive_node_id(priv_bin)`.
- Генерация новой пары ключей: `sc_generate_keypair(&mykeys)`.
- Файл читается целиком в память, модифицируется через `insert_or_replace_option`, затем пишется обратно. Это атомарно: либо файл полностью перезаписан, либо остался нетронутым (при ошибке).
- Редактирование in-place через `memmove`: строки не сдвигают остальной конфиг, только вставляются/заменяются нужные опции внутри секции `[global]`.
- Для нового файла создаётся минимальный `[global]` с именем узла из `my_node_name` (или `"utun"` если не задано).
Перед записью итоговый текст проверяется строгими декодерами и штатным парсером.
Это проверка полей идентичности и необходимых параметров REALITY; парсер продолжает
логировать и пропускать некоторые ошибки прочих опций.
## 3. API
## Запись и ошибки
### Функции
config_ensure_keys_and_node_id(filename) принимает непустой путь. Вызывающая сторона
исключает параллельную запись конфигурации. Возврат 0 означает корректные значения и,
если требовалось обновление, успешную запись. Возврат -1 означает ошибку с диагностикой CONFIG.
| Функция | Назначение |
|---------|------------|
| `config_ensure_keys_and_node_id(filename)` | **Основная функция.** Читает конфиг, проверяет/генерирует ключи и node_id, записывает обратно. Возвращает 0 при успехе, -1 при ошибке. |
| `bytes_to_hex(bytes, len, hex_str, hex_len)` | Вспомогательная: конвертация бинарного массива в hex-строку. Безопасна для нулевых указателей и маленьких буферов. |
Новый файл создаётся только при ENOENT. Ошибка доступа, чтения, выделения памяти или
закрытия входного файла прекращает операцию. Временный файл создаётся эксклюзивно
рядом с исходным. Проверяются запись, fflush, синхронизация и fclose до замены имени.
### Внутренние функции (static)
На POSIX применяется rename, затем fsync каталога:
- До успешного rename исходный файл при ошибках сохраняется.
- Ошибка fsync каталога после rename возвращает -1 и логируется с replaced=1.
Файл уже заменён; устойчивость замены к аварии не подтверждена.
- Сохраняются uid/gid и обычные права доступа (0777); новый файл имеет 0600.
ACL, xattrs и специальные биты режима не копируются.
- Временный файл удаляется при ошибках до публикации; ошибка удаления логируется.
| Функция | Назначение |
|---------|------------|
| `is_valid_priv_key(key)` | Проверяет, что строка — 64 hex-символа |
| `is_valid_pub_key(key)` | То же для публичного ключа |
| `is_valid_node_id(node_id)` | Проверяет `node_id != 0` |
| `write_mem_to_file(filename, buffer, size)` | Пишет буфер в файл (одной операцией) |
| `find_section_global(buf, buf_len)` | Ищет позицию после `[global]` в текстовом буфере |
| `find_option(buf, buf_len, option)` | Ищет строку `option=...` в буфере |
| `insert_or_replace_option(buf, buf_len, buf_capacity, option, value)` | Заменяет значение опции при наличии, либо вставляет новую строку после `[global]`. Работает с `memmove`/`memcpy`, перевыделяет буфер при необходимости. |
На Windows используются _commit и ReplaceFileA для существующего файла либо
MoveFileExA с MOVEFILE_WRITE_THROUGH для нового. Модуль не расширяет гарантии
восстановления после ошибок этих Windows API.
### Зависимости
Если требуется запись, символические ссылки/reparse points и файлы с несколькими
жёсткими ссылками отклоняются. Проверка исправного файла по ссылке возможна без записи.
- `secure_channel.h` — `sc_generate_keypair()`, `sc_compute_public_key_from_private()`, `sc_derive_node_id()`
- `config_parser.h` — `parse_config_from_buf()`, `free_config()`, структуры `utun_config`/`global_config`
- `lib/mem.h` — `u_malloc`, `u_realloc`, `u_free`
- `lib/debug_config.h` — `DEBUG_*`
Логи показывают причину генерации, изменяемые поля, этап ошибки и факт замены файла.
Значения приватных ключей updater не выводит.
## Вспомогательная функция
bytes_to_hex(bytes, len, hex_str, hex_len) записывает 2*len строчных HEX-символов и NUL.
Буферы не перекрываются. bytes и hex_str обязательны; hex_len задаёт ёмкость результата.
При недостаточной ёмкости или NULL функция обнуляет доступный hex_str[0]; усечения нет.
Проверка ёмкости не вычисляет переполняющееся 2*len+1. При len=0 результат пустой.
## Проверки
tests/test_config_updater.c проверяет сохранение идентичности, регистр, пробелы,
секции и дубли, CRLF, повторный вызов, строгий HEX/списки, длинные строки/NUL, ссылки,
права доступа, ошибки файловых операций и отказы памяти. Внедрение ошибок через
Linux/ELF linker wrappers не добавляет тестовых веток в рабочий код.
tests/test_reality_config.c проверяет базовые сценарии генерации REALITY.