You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

35 KiB

db_sync — децентрализованная реплицируемая таблица с цепным хешированием

1. Архитектура

1.1 Обзор

                              UTUN_INSTANCE
                                   │
                              ┌────▼────┐
                              │ DB_SYNC │  (глобальный контекст, один на инстанс)
                              └────┬────┘
                                   │
                  ┌────────────────┼────────────────┐
                  ▼                ▼                ▼
          ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
          │DB_SYNC       │ │DB_SYNC       │ │DB_SYNC       │
          │INSTANCE      │ │INSTANCE      │ │INSTANCE ...  │
          │msg_GENERAL   │ │msg_SECRET    │ │              │
          └──────┬───────┘ └──────┬───────┘ └──────────────┘
                 │                │
        ┌────────┼────────┐       │
        ▼        ▼        ▼       ▼
   ┌────────┐┌────────┐┌──────┐
   │SI_PEER ││SI_PEER ││SI_...│    (per-instance peer state)
   │node_A  ││node_B  ││      │
   └────────┘└────────┘└──────┘

DB_SYNC — глобальный модуль (struct, один на UTUN_INSTANCE):

  • inst — указатель на UTUN_INSTANCE
  • db — SQLite-соединение (своё или shared inst->topo_sqlite_db)
  • instances[] — динамический массив DB_SYNC_INSTANCE
  • peer_check_timer — периодическая проверка (5с), ищет несинхронизированных пиров
  • done_cbks — связанный список коллбэков завершения синхронизации

DB_SYNC_INSTANCE — одна таблица синхронизации:

  • table_name — имя SQLite-таблицы (например msg_GENERAL)
  • hash — instance_hash = первые 8 байт SHA256(name || id_be) для маршрутизации сообщений
  • next_id — автоинкрементный ID записей
  • last_timestamp_ms — последний использованный timestamp (монотонный)
  • peers[] — динамический массив SI_PEER (состояние синхронизации с каждым пиром)
  • recalc — отложенный пересчёт цепных хешей (асинхронно, батчами по 50)
  • ttl_timer — периодическая TTL-очистка (каждый час)
  • on_insert / on_insert_arg — коллбэк при вставке записи

SI_PEER — состояние пира в контексте инстанса:

  • node_id — идентификатор пира
  • synced_pos — позиция (0-based) до которой данные синхронизированы
  • verified_pos — последняя подтверждённая позиция (совпадение хешей)
  • last_peer_count — последнее известное количество записей у пира
  • sync_state — 0=idle, 1=syncing, 2=synced
  • sync_start_tb — время начала синхронизации (для детекции таймаутов)
  • retry_count — счётчик ретраев (зарезервировано, не используется)

1.2 Структура данных (SQLite)

Каждый инстанс — это SQLite-таблица:

CREATE TABLE "msg_GENERAL" (
    timestamp        INTEGER NOT NULL,    -- мс, монотонный (NTP или local)
    node_id          INTEGER NOT NULL,    -- author node_id (8 байт)
    id               INTEGER NOT NULL,    -- автоинкремент внутри инстанса
    chain_hash       BLOB NOT NULL,       -- SHA256(prev_hash || id || ts || author || sig)
    flags            INTEGER DEFAULT 0,   -- DB_REC_FLAG_WAS_SENT (0x01)
    data             BLOB,                -- JSON-данные записи
    author_signature BLOB NOT NULL,       -- Ed25519(ts || data), 64 байта
    local_attrs      TEXT DEFAULT '',     -- локальные атрибуты (не реплицируются)
    delivered_peers  INTEGER DEFAULT 0,   -- счётчик пиров, принявших PUSH
    delivery_chain   TEXT DEFAULT '',     -- цепочка peer_id hex через запятую
    PRIMARY KEY (timestamp, author_signature)
);
CREATE INDEX idx_msg_GENERAL_ttl ON msg_GENERAL (node_id, timestamp);

Цепной хеш:

chain_hash[pos] = SHA256(chain_hash[pos-1] || id || timestamp || author || author_signature)
chain_hash[0]   = SHA256(0x00..00 [32] || id_0 || ts_0 || author_0 || sig_0)

Упорядочение: ORDER BY timestamp, author_signature.

1.3 Ключевые механизмы

Механизм Описание
Цепной хеш Каждая запись ссылается на хеш предыдущей — защита от вставки/удаления в середине цепочки
Ed25519-подписи Каждая запись подписана автором. Проверяется при вставке (и локальной, и от пира)
Async recalc Пересчёт chain_hash батчами по 50 записей (через uasync_set_timeout) — не блокирует event loop
Master/Slave Узел с бОльшим node_id — мастер (шлёт INIT_SYNC). Узел с меньшим — слейв (шлёт REQUEST_SYNC)
Периодическая проверка Каждые 5 секунд — поиск пиров с sync_state=0 и запуск синхронизации, детекция таймаутов (15с)
TTL-очистка Каждый час — удаление неподтверждённых (flags&1==0) записей локального автора старше db_sync_ttl
Lazy-register При получении сообщения с неизвестным instance_hash — поиск таблицы msg_<ch_id> с совпадающим хешем
Push Новая локальная запись немедленно рассылается всем пирам в sync_state >= 1
Delivery tracking delivered_peers / delivery_chain — отслеживание доставки PUSH каждому пиру

1.4 Интеграция с ETCP

  • Service ID: ETCP_RT_ID_DB_SYNC = 0x20
  • Биндинг: etcp_bind(inst, 0x20, db_sync_recv_cb) — приём сообщений
  • Connection callbacks: etcp_add_conn_status_cbk(inst, db_sync_on_conn_status, NULL) — отслеживание UP/DOWN соединений
  • Отправка: через etcp_send() в прямое P2P-соединение
  • Маршрутизация: каждый пакет начинается с [svc:1][hash_be:8], hash идентифицирует инстанс на приёмной стороне

2. Диаграмма состояний синхронизации

2.1 Состояния пира (sync_state)

      ┌─────────────────────────────────────────────┐
      │                                             │
      ▼                                             │
  ┌───────┐   INIT_SYNC/REQUEST_SYNC    ┌─────────┐ │
  │ IDLE  │ ──────────────────────────► │ SYNCING │ │
  │ (0)   │                             │   (1)   │ │
  └───────┘                             └────┬────┘ │
      ▲                                     │      │
      │                           ┌─────────┼──────┘
      │                           │         │
      │               ┌───────────▼──┐  ┌───▼──────────┐
      │               │ SYNC_DONE    │  │ TIMEOUT+ERROR │
      │               │ matched ✓    │  │ (15s)         │
      │               │ (2)          │  │ reset → IDLE  │
      │               └──────────────┘  └───────────────┘
      │                                             │
      └─────────────────────────────────────────────┘
      CONN DOWN / ERROR (DB_ERR_NOT_FOUND, DB_ERR_DISABLED)

2.2 Диаграмма протокола синхронизации

MASTER (node_id > peer)                  SLAVE (node_id < peer)
───────────────────────                  ──────────────────────

conn UP → initiate sync
│
├─ INIT_SYNC ──────────────────────────►
│  [my_count:4]                          │ flush recalc
│                                        │ определяет tp = min(peer_c, my_c) - 1
│                                        │ вычисляет ch8_at_tp
│                                        │ строит sparse hashes (интервалы: 1,1,1,2,2,4×8)
│                                        │
│  ◄────────────────────────── INIT_RESP │
│     [tp:4][ch8:8][has_tail:1]          │
│     [sparse_cnt:1][(pos,ch8)*]         │
│                                        │
│  flush recalc                          │
│  ┌─ peer ch8 == 0? ──► send ALL data  │
│  ├─ ch8 match + no_tail? ──► DONE     │
│  ├─ ch8 match + has_tail? ──► send    │
│  │   from tp+1, want_from=tp+1         │
│  └─ ch8 mismatch? ──► находим         │
│      первую разницу через sparse      │
│      hashes → fm (first mismatch)      │
│      vp = fm-1, send from fm           │
│                                        │
│  SEND_DATA ──────────────────────────► │
│  [from:4][count:2][vp:4]               │ вставляет записи, проверяет подписи
│  [want_from:4][records...]             │ проверяет want_from:
│                                        │   want≠NONE → отправляет встречный SEND_DATA
│                                        │   want=NONE + ss=1 → SYNC_DONE
│                                        │
│  ◄────────────────────────── SEND_DATA │
│     (встречные данные при want→NONE)   │
│                                        │
│  ┌─ want=NONE + ss=1 → SYNC_DONE ────►│
│  │  [mc:4][ch8_last:8]                 │ сверяет mc==pc и ch8==pch8
│  │                                      │ match → ss=2, DONE
│  │                                      │ mc<pc → request tail
│  │                                      │ mc>pc → send tail
│  └─────────────────────────────────────│

2.3 Временная диаграмма жизненного цикла

┌─ init ─────────────────────────────────────────────────────────────────────┐
│ db_sync_init(inst)                                                          │
│   ├─ открывает SQLite (shared или свой chats.db)                            │
│   ├─ etcp_bind(inst, 0x20, recv_cb)                                        │
│   ├─ etcp_add_conn_status_cbk(conn_status)                                  │
│   └─ запускает peer_check_timer (5s)                                        │
│                                                                             │
│ ... позже, при создании канала ...                                          │
│                                                                             │
│ db_sync_instance_add(inst, "msg_GENERAL", hash, 1)                          │
│   ├─ CREATE TABLE IF NOT EXISTS...                                          │
│   ├─ загружает next_id = MAX(id)+1                                         │
│   ├─ db_verify_chain() — проверка целостности цепных хешей                 │
│   ├─ запускает TTL-таймер (1 час)                                           │
│   └─ для всех активных соединений: si_peer_add() + initiate_sync()          │
├────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─── DB_SYNC_INSTANCE активен ───────────────────────────────────────────┐│
│  │                                                                         ││
│  │  conn UP ──► on_conn_up()                                               ││
│  │    └─ для всех enabled инстансов:                                       ││
│  │         si_peer_add() → initiate_sync()                                 ││
│  │                                                                         ││
│  │  conn DOWN ──► on_conn_down()                                           ││
│  │    └─ сброс sync_state для всех инстансов                               ││
│  │                                                                         ││
│  │  recv_cb() — приём сообщений                                            ││
│  │    ├─ извлекает instance_hash, type, payload                            ││
│  │    ├─ lazy-register если инстанс не найден                              ││
│  │    └─ диспетчеризация по type                                           ││
│  │                                                                         ││
│  │  peer_check_timer (каждые 5с)                                           ││
│  │    ├─ находит пиров c sync_state=0, запускает sync                     ││
│  │    └─ детектит таймауты (15s), сбрасывает sync_state                    ││
│  │                                                                         ││
│  │  ttl_timer (каждый час)                                                 ││
│  │    └─ удаляет неподтверждённые локальные записи старше TTL              ││
│  │                                                                         ││
│  │  recalc_timer (асинхронно, батчи по 50)                                 ││
│  │    └─ пересчитывает chain_hash для записей после вставки                ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│                                                                             │
│ db_sync_instance_remove(si)                                                 │
│   ├─ отменяет таймеры                                                       │
│   ├─ освобождает peers[]                                                   │
│   └─ сдвигает массив instances[] (таблица БД не удаляется)                  │
│                                                                             │
│ db_sync_destroy(inst)                                                       │
│   ├─ etcp_unbind(0x20)                                                      │
│   ├─ останавливает все таймеры                                              │
│   ├─ освобождает все инстансы + done_cbks                                  │
│   ├─ закрывает SQLite                                                      │
│   └─ u_free(db)                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

3. Протокол (форматы кодограмм)

3.1 Общий заголовок ETCP

Каждое сообщение db_sync инкапсулируется в ETCP-дейтаграмму:

┌─────────────────┬──────────────┬──────────────────────────────┐
│ svc_id (1 byte) │ hash_be (8)  │ payload (variable)           │
│ ETCP_RT_ID=0x20 │ instance_hash│ зависит от type              │
└─────────────────┴──────────────┴──────────────────────────────┘

instance_hash = первые 8 байт SHA256(table_name || id_be) в big-endian. Это позволяет приёмной стороне найти DB_SYNC_INSTANCE по хешу.

3.2 Типы сообщений

#define Value Направление Назначение
DB_MSG_INIT_SYNC 0x01 Master→Slave Инициирует синхронизацию
DB_MSG_INIT_RESP 0x02 Slave→Master Ответ с хешами на точке расхождения
DB_MSG_SEND_DATA 0x04 ↔ Передача батча записей
DB_MSG_PUSH 0x05 →Peers Рассылка новой записи всем synced-пирам
DB_MSG_ACK_PUSH 0x06 →Author Подтверждение получения PUSH
DB_MSG_SYNC_DONE 0x07 ↔ Завершение синхронизации
DB_MSG_ERROR 0x08 ↔ Ошибка (NOT_FOUND, DISABLED)
DB_MSG_REQUEST_SYNC 0x09 Slave→Master Запрос на инициацию синхронизации

3.3 DB_MSG_INIT_SYNC (0x01)

Отправитель: мастер (node_id > peer_id) Назначение: сообщить пиру своё количество записей для поиска точки расхождения

┌──────┬──────────────┐
│ 0x01 │ count (4 BE) │
└──────┴──────────────┘
      total: 5 bytes
  • count — количество записей в таблице мастера (uint32, big-endian)

3.4 DB_MSG_INIT_RESP (0x02)

Отправитель: слейв (node_id < peer_id) Назначение: вернуть хеш на точке пересечения tp и разреженные хеши для бинарного поиска расхождения

┌──────┬──────────────┬──────────────┬─────────────┬─────────────┬──────────────────────────────────┐
│ 0x02 │ tp (4 BE)    │ ch8_at_tp(8) │ has_tail(1) │ sparse_cnt  │ sparse_entries[sparse_cnt]       │
│      │              │              │             │ (1)         │ { pos(4BE), ch8(8) } × sparse_cnt│
└──────┴──────────────┴──────────────┴─────────────┴─────────────┴──────────────────────────────────┘
      total: 14 + 12×sparse_cnt bytes (max 206)
  • tp — точка пересечения: min(peer_count, my_count) - 1 (uint32, network byte order)
  • ch8_at_tp — первые 8 байт chain_hash на позиции tp (uint64, host byte order)
  • has_tail — 1 если у слейва есть записи после tp (uint8)
  • sparse_cnt — количество разреженных точек (uint8)
  • sparse_entries — массив {pos:4, ch8:8} от tp назад с интервалами: 1, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 (макс. 16)

Пример sparse-интервалов при tp=10:

tp=10 → entry[0]: pos=9  (tp - 1)
         entry[1]: pos=8  (tp - 2)
         entry[2]: pos=7  (tp - 3)
         entry[3]: pos=5  (tp - 5)
         entry[4]: pos=3  (tp - 7)
         entry[5]: pos=0  (tp - 11) — только если tp достаточно велико

3.5 DB_MSG_SEND_DATA (0x04)

Отправитель: любой узел Назначение: передать батч записей (до 32) и/или запросить встречные данные

┌──────┬───────────┬───────────┬───────────┬──────────────┬──────────────────────────┐
│ 0x04 │ from(4BE) │ count(2)  │ vp(4BE)   │ want_from(4) │ records[count]            │
│      │           │           │           │              │ ≤32 штук                  │
└──────┴───────────┴───────────┴───────────┴──────────────┴──────────────────────────┘
      header: 14 bytes + records
  • from — начальная позиция записей (uint32, network byte order)
  • count — количество записей в этом батче (uint16, network byte order), ≤ 32
  • vp — verified position (uint32, BE): последняя позиция, где хеши совпали. 0xFFFFFFFF если нет
  • want_from — запрос встречных данных от пира начиная с этой позиции. 0xFFFFFFFF = DB_WANT_FROM_NONE (не запрашивать)
  • records — массив записей

Формат одной записи (record):

┌───────┬───────┬──────────┬──────────┬────────────┬──────────┬──────────┐
│ id(8) │ ts(8) │ author(8)│ dlen(4BE)│ data(dlen) │ sig_len  │ sig(64)  │
│       │       │          │          │            │ =64 (1)  │          │
└───────┴───────┴──────────┴──────────┴────────────┴──────────┴──────────┘
      минимальный размер: 28 + 1 + 64 = 93 bytes (при dlen=0)
  • id — идентификатор записи (uint64, host byte order)
  • ts — timestamp в мс (uint64, host byte order)
  • author — node_id автора (uint64, host byte order)
  • dlen — длина JSON-данных (uint32, network byte order)
  • data — JSON-данные (dlen байт)
  • sig_len — длина подписи, всегда 64 (uint8)
  • sig — Ed25519-подпись: Ed25519(ts[8] || data[dlen]), 64 байта

Note: поля id, ts, author в host byte order (LE на x86), а dlen в network byte order (BE).

3.6 DB_MSG_PUSH (0x05)

Отправитель: автор записи → все synced-пиры Назначение: рассылка новой записи в реальном времени

Формат идентичен одной record из SEND_DATA:

┌──────┬───────┬───────┬──────────┬──────────┬────────────┬──────────┬──────────┐
│ 0x05 │ id(8) │ ts(8) │ author(8)│ dlen(4BE)│ data(dlen) │ sig_len  │ sig(64)  │
│      │       │       │          │          │            │ =64 (1)  │          │
└──────┴───────┴───────┴──────────┴──────────┴────────────┴──────────┴──────────┘
      минимальный размер: 29 + 1 + 64 = 94 bytes (при dlen=0)

3.7 DB_MSG_ACK_PUSH (0x06)

Отправитель: получатель PUSH → автору Назначение: подтверждение получения PUSH-записи

┌──────┬──────────┬──────────────┐
│ 0x06 │ ts (8)   │ author (8)   │
└──────┴──────────┴──────────────┘
      total: 17 bytes
  • ts — timestamp записи (uint64, host byte order)
  • author — node_id автора (uint64, host byte order)

При получении ACK_PUSH автор помечает запись флагом DB_REC_FLAG_WAS_SENT (0x01) и обновляет delivery_chain/delivered_peers.

3.8 DB_MSG_SYNC_DONE (0x07)

Отправитель: любой узел Назначение: финальная сверка — сравнение количества записей и последнего chain_hash

┌──────┬─────────────┬──────────────────┐
│ 0x07 │ count (4BE) │ chain_hash8 (8)  │
└──────┴─────────────┴──────────────────┘
      total: 13 bytes
  • count — количество записей в таблице отправителя (uint32, network byte order)
  • chain_hash8 — первые 8 байт последнего chain_hash (uint64, host byte order)

Обработка:

  • count == my_count && ch8 == my_ch8 → sync завершён, ss→2, fire db_sync_done_cb
  • my_count < peer_count → запрашиваем хвост (SEND_DATA from=my_count, want=my_count)
  • my_count > peer_count → отправляем хвост (SEND_DATA from=peer_count)
  • count == my_count && ch8 != my_ch8 → принимаем как сошедшееся (конвергенция)

3.9 DB_MSG_ERROR (0x08)

Отправитель: любой узел Назначение: сигнализировать об ошибке (инстанс не найден или выключен)

┌──────┬──────────┐
│ 0x08 │ code (1) │
└──────┴──────────┘
      total: 2 bytes

Коды ошибок:

#define Value Значение
DB_ERR_NOT_FOUND 0x01 Инстанс с таким hash не найден
DB_ERR_DISABLED 0x02 Инстанс существует, но disabled

При получении ERROR сбрасывается sync_state пира в 0.

3.10 DB_MSG_REQUEST_SYNC (0x09)

Отправитель: слейв → мастер Назначение: запросить инициацию синхронизации (слейв говорит мастеру «начни sync»)

┌──────┐
│ 0x09 │
└──────┘
      total: 1 byte (без payload)

При получении REQUEST_SYNC мастер вызывает initiate_sync() → шлёт INIT_SYNC.


4. Алгоритм синхронизации (по шагам)

4.1 Запуск

  1. При conn UP для каждого enabled инстанса: si_peer_add() → initiate_sync()
  2. initiate_sync():
    • Если node_id > peer_id (мастер): шлёт INIT_SYNC с my_count
    • Если node_id < peer_id (слейв): шлёт REQUEST_SYNC

4.2 Обработка INIT_SYNC (слейв)

  1. db_sync_flush_recalc() — синхронно завершает все отложенные пересчёты
  2. Вычисляет tp = min(peer_count, my_count) - 1 (точка пересечения)
  3. Вычисляет ch8_at_tp — chain_hash8 на позиции tp
  4. Определяет has_tail = (my_count > tp + 1)
  5. Строит разреженные хеши: от tp назад с интервалами 1,1,1,2,2,4×8
  6. Отправляет INIT_RESP с tp, ch8_at_tp, has_tail, sparse_hashes
  7. Устанавливает sync_state = 1 для этого пира

4.3 Обработка INIT_RESP (мастер)

  1. db_sync_flush_recalc() — синхронно завершает все отложенные пересчёты
  2. Извлекает tp, peer_ch8, has_tail, sc, sparse_hashes
  3. Вычисляет my_ch8_at_tp

Ветки:

  • peer_ch8 == 0 && sc == 0 (пир пуст): шлёт все свои данные батчами по 32 записи. Последний батч с want_from = my_count (запрос встречных данных, которые будут пустыми → SYNC_DONE)
  • my_ch8 == peer_ch8 (совпадение на tp):
    • !has_tail && my_count <= tp+1: обе стороны идентичны → сразу SYNC_DONE
    • has_tail: отправляем свои записи после tp и запрашиваем записи пира (want_from = tp+1)
  • my_ch8 != peer_ch8 (расхождение): ищем первую различающуюся позицию fm через sparse-хеши. vp = fm-1. Отправляем данные с fm и запрашиваем данные пира с fm.

4.4 Обработка SEND_DATA

  1. db_sync_flush_recalc() — синхронно завершает все отложенные пересчёты
  2. Парсит from, count, vp, want_from
  3. Для каждой записи (макс. 32): si_parse_record() → db_record_insert()
    • Проверка Ed25519-подписи (если неверна — удаление записи)
    • Проверка дубликата по (timestamp, author_signature)
    • Вычисление chain_hash
    • Вставка в SQLite, запуск async recalc
    • Вызов on_insert коллбэка
  4. Корректирует synced_pos (и соседних пиров, если запись вставлена не в конец)
  5. Если want_from != DB_WANT_FROM_NONE: отправляет встречный SEND_DATA
  6. Если want_from == DB_WANT_FROM_NONE и sync_state == 1: отправляет SYNC_DONE

4.5 Обработка SYNC_DONE

  1. db_sync_flush_recalc()
  2. Сравнивает my_count, my_ch8_last с peer_count, peer_ch8_last
  3. Совпало → sync_state=2, fire db_sync_done_cb
  4. my < peer → запрашиваем хвост SEND_DATA(from=my_count, want=my_count)
  5. my > peer → отправляем хвост SEND_DATA(from=peer_count)
  6. count совпало но хеши разные → принимаем как converged

4.6 PUSH (реальное время)

  1. При локальной вставке (db_sync_insert_signed):
    • Формирует PUSH-сообщение
    • Рассылает всем пирам с sync_state >= 1
    • Обновляет delivery_chain для каждого пира
  2. При получении PUSH:
    • Вставляет запись через db_record_insert()
    • Шлёт ACK_PUSH обратно автору
    • Если запись вставлена в середину (не в конец) — сбрасывает synced_pos всех пиров до позиции вставки-1
  3. При получении ACK_PUSH:
    • Помечает запись флагом WAS_SENT
    • Обновляет delivery_chain

5. Безопасность

  1. Ed25519-подписи: каждая запись подписана Ed25519(timestamp || json_data). Проверяется всегда — и при локальной вставке, и при приёме от пира. Невалидные записи отбрасываются и удаляются из БД.
  2. Цепной хеш: SHA256-цепочка защищает от вставки/удаления записей в середине истории. При проверке (db_sync_chain_verify) или при обнаружении расхождения во время отправки — запускается пересчёт с позиции ошибки.
  3. Публичные ключи: Ed25519 публичный ключ пира получается из трёх источников (по приоритету):
    • Локальный inst->my_ed25519_pubkey (для своих записей)
    • topo_node_sqlite_get_ed25519_pubkey() (из БД узлов)
    • conn->peer_ed25519_pubkey (из ETCP handshake)
  4. Отсутствие удаления/редактирования: записи не редактируются и не удаляются явно — только TTL-очистка неподтверждённых.

6. Конфигурация

[global]
db_sync_enabled = 1       # включить модуль (по умолчанию 0)
db_sync_ttl = 86400       # TTL неподтверждённых записей в секундах (по умолчанию 86400)
db_path = /var/lib/utun   # путь к БД (по умолчанию /tmp/utun_db_sync)