6.2 Діалоги — call-level-стан через кілька транзакцій¶
[!IMPORTANT] SIP-виклик — це кілька транзакцій:
INVITE, що його піднімає, in-dialogre-INVITE'и,BYE, що його завершує.tmтрекає кожну транзакцію незалежно.dialog— модуль, що склеює їх разом — щоб проксі могло відповісти «чи цейBYE— частина виклику, який я авторизував раніше?» і «скільки часу триває цей виклик?» — не перевіряючи auth на кожному повідомленні.
Що дає dialog поверх tm¶
Один tm не може відповісти на call-level-питання. Він знає лише про транзакції: BYE, що прилетів через годину після INVITE, — це нова транзакція, і tm поняття не має, що вони пов'язані. Зв'язок живе в SIP-dialog-ідентифікаторах — Call-ID, From-tag, To-tag — і хтось має їх пам'ятати.
Цей «хтось» — модуль dialog. Коли ви викликаєте dlg_manage() на запиті в request_route, dialog відкриває dialog-record для виклику, проіндексований за цими ідентифікаторами, який переживає кожну транзакцію виклику:
flowchart TB
Inv["INVITE → транзакція в tm"] --> Tx1["tm cell #1<br/>(короткоживучий)"]
Re["re-INVITE → транзакція в tm"] --> Tx2["tm cell #2"]
Bye["BYE → транзакція в tm"] --> Tx3["tm cell #3"]
Tx1 -.-> Dlg
Tx2 -.-> Dlg
Tx3 -.-> Dlg
Dlg["dialog cell<br/>(довгоживучий, у shm)<br/>Call-ID + From-tag + To-tag<br/>+ state + timers"]
classDef txn fill:#bf8700,stroke:#bf8700,color:#fff
classDef dlg fill:#1f6feb,stroke:#1f6feb,color:#fff
class Tx1,Tx2,Tx3 txn
class Dlg dlg
Dialog-record переживає транзакцію, що його створила. INVITE'овий tm-cell виходить з WAIT-таймера через ~30 секунд після 200 OK; dialog лишається живим увесь час виклику — типово хвилини чи години.
State machine діалогу¶
Dialog проходить три стани:
- EARLY —
INVITEпішов, прилетіли provisional-відповіді, final ще немає. - CONFIRMED —
INVITEотримав 2xx final-відповідь. ACK обмінялися. Виклик піднятий. - TERMINATED —
BYEотриманий і відповідь дана, або dialog тайм-аут'нувся.
Кожен dlg_manage() хукається у tm, щоб отримувати сповіщення про потрібні події:
- На 1xx — лишається EARLY, оновлюються timestamp'и.
- На 2xx final + ACK — у CONFIRMED, стартує inactivity-таймер.
- На final-відповідь BYE — у TERMINATED, dialog-cell звільняється.
- На стрільбу final-response-таймера
tmбез 2xx — fail назад у TERMINATED, free.
Stat-переходи — це й те, що робить dialog «stateful»-модулем у спосіб, у який tm ним не є. tm знає лише «транзакція в роботі» чи «транзакція завершена». dialog знає різницю між «дзвонить» і «розмова йде».
Що тримає dialog-cell¶
Dialog-cell у shm має:
- Ідентифікатори. Call-ID, From-tag, To-tag. Це lookup-ключі.
- Endpoint URI. Контакт caller'а, контакт callee, request-URI на момент setup'у.
- Route set. Заголовки
Record-Route/Route, як їх встановив проксі-ланцюг. Потрібні для правильного routing'у in-dialog-запитів. - Timestamp'и. Створення, остання активність, час підтвердження.
- State. EARLY / CONFIRMED / TERMINATED.
- Змінні. Per-dialog scratchpad —
$dlg_var(name)у cfg. Корисно для наклеювання даних на виклик, що мають пережити окремі транзакції. - Profile membership. У які
dlg_profile'и dialog зарахований — для «скільки одночасних викликів на gateway».
Cell використовує той самий shm-аллокатор і ту саму per-bucket-локовану hash, що й tm. hash_size у dialog — окремий від tm'ого.
Sticky vs. transient — що трекати¶
dialog дорогий у shm, якщо вмикати на кожен виклик. Рішення — per-call: виклик dlg_manage() у request_route — це opt-in. Більшість розгортань не трекають кожен виклик — трекають ті, що мають значення.
Причини трекати dialog:
- Routing in-dialog-запитів. re-INVITE'и і BYE, що мають піти тим же шляхом, що оригінал.
- Call accounting. Per-call duration, billing, CDR generation.
- Per-call rate-limiting. dlg_profile_get("calls_per_user") для обмеження одночасних викликів per user.
- Виявлення hijacking'у. Перевірка, що BYE приходить від сторони, що була в оригінальному INVITE.
- State topology hiding'а. Коли в грі topos, dialog — природне місце для повішення mapping'у (більше — в розділі 8.1).
Причини не трекати: - Виклик — stateless-форвард, і Kamailio не треба впізнавати BYE. - shm-бюджет тісний, а call count величезний (LSR — Least-Cost-Router-проксі).
Keepalive — виявлення тихих провалів¶
SIP-виклик може мовчати годинами. UAC зареєстрований, UAS прийняв, обидва ендпоінти шлють RTP кудись — а Kamailio й гадки не має, чи dialog ще живий. Якщо BYE загубився (UDP, network partition, NAT timeout), dialog сидітиме у shm назавжди.
dialog розв'язує це через OPTIONS-based keepalive: опційно періодично шле SIP OPTIONS-пінг кожному ендпоінту у CONFIRMED-діалогах. Якщо ендпоінт не відповідає у налаштований тайм-аут — dialog мітиться мертвим і термінується.
Ціна: одна in-flight tm-транзакція per dialog per keepalive-інтервал. На тисячах одночасних викликів і 5-хвилинному keepalive це значущий трафік — але це єдиний спосіб виявити partition-induced dead calls без join'у на БД UAS.
Persistence через restart¶
За дефолтом dialog-record'и живуть у shm і втрачаються при рестарті Kamailio. Для довгих викликів це погано: ви рестартуєте, і будь-який виклик, що був активним, стає необнулюваним з боку Kamailio — BYE не зматчиться з жодним dialog-record'ом і може зафор'юардитися неправильно.
Фікс — DB-backing у dialog, аналогічний патерну usrloc (наступний розділ). Dialog'и пишуться в таблицю dialog або на кожне state-change (синхронно, дорого), або періодично (асинхронно, типовий продакшн-режим). На рестарті таблиця читається назад у shm на стадії child_init(), активні dialog'и реконструюються.
[!WARNING] DB-backed dialog-state — «best-effort», не транзакційний. Між останнім flush'ем і рестартом нещодавно-підтверджені dialog'и можуть втратитися. Для більшості операторів це прийнятно — виклики, що дзвонять у момент рестарту, відновляться через стан ендпоінтів, а Kamailio просто не матиме що про них сказати.
Де dialog і tm перетинаються¶
Обидва модулі співпрацюють явно:
dialogреєструє callback'и уtmдля transaction-подій, що його цікавлять (final-відповіді на INVITE, ACK, BYE-термінація).- Коли
tmчистить cell, він повідомляєdialog, щоб dialog-record оновив стан. dialogне чіпаєtm-cell'и напряму; іде через зареєстрований callback-API.
Цей розділ означає, що можна ганяти tm без dialog (statelessish-проксі без call-tracking), але не можна ганяти dialog без tm — dialog'ові state-переходи драйвляться транзакціями, які спостерігає tm.
Наступний розділ бере іншу довгоживучу shm-backed-структуру — кеш контактів реєстратора — і показує, як usrloc тримає in-memory state синхронним з БД, не беручи лока на кожен REGISTER.