Перейти до змісту

3.2 Розпарсене повідомлення

[!IMPORTANT] Kamailio не парсить SIP-повідомлення повністю на прийомі. Він робить мінімум, щоб зрозуміти, що повідомлення well-formed і якого воно типу. Заголовки, URI, тіло парсяться на першу вимогу. Це найбільша причина, чому route'и, що чіпають кілька заголовків, працюють так швидко.

struct sip_msg

Кожне отримане SIP-повідомлення живе в struct sip_msg, виділеній у pkg-купі воркера (див. розділ 2.2). Це центральна структура даних обробки повідомлень — кожна функція модуля приймає її першим аргументом, кожна псевдо-змінна — це по суті getter на її поля, кожен lump висить на ній.

Що в ній (схематично):

struct sip_msg {
    char       *buf;          // вказівник на оригінальні отримані байти
    unsigned    len;          // довжина buf
    msg_type    type;         // запит чи відповідь
    method_id   first_line;   // метод+URI (запит) або статус+reason (відповідь)
    hdr_field  *headers;      // linked list розпарсених заголовків
    hdr_field  *callid;       // shortcut → заголовок Call-ID (NULL до парсингу)
    hdr_field  *to;           // shortcut → заголовок To
    hdr_field  *from;         // shortcut → заголовок From
    hdr_field  *cseq;
    hdr_field  *contact;
    /* …ще десяток shortcut'ів… */
    str         body;         // вказівник + довжина в buf; парсинг тіла окремий
    lump       *add_rm;       // lumps для форвард-повідомлення (наступний розділ)
    lump_rpl   *reply_lump;   // lumps для відповіді
    /* …flag'и, dst_addr, send_socket, force_send_socket, … */
};

Сама структура маленька. Байти, на які вона вказує — buf — це оригінал отриманого повідомлення, нерухомий. Ніщо в рантаймі не копіює буфер; усе — це offset'и та вказівники в одні й ті самі байти.

[!TIP] Коли ви пишете $ru (request URI), $tu (To URI), $hdr(X-Foo) у routing-скрипті — ви тригерите Kamailio розпарсити на першу вимогу, якщо ще не. Getter псевдо-змінної дзвонить parse_uri(), parse_to() чи parse_headers(msg, HDR_X_F) під капотом, результат кешується на sip_msg, і всі наступні звернення вже безкоштовні.

First-pass-парсинг

Коли receive_msg() працює (див. розділ 3.1), він дзвонить parse_msg(), який робить лише це:

  1. Знаходить першу строку — рядок запиту (METHOD URI SIP/2.0) або status-рядок (SIP/2.0 STATUS REASON). Перевіряє, що вона парситься.
  2. Йде по header-секції, реєструє в linked list ім'я і byte range кожного заголовка. Значення заголовків не парсяться. Парсер знає, де починається й закінчується To:, але не знає, хто там.
  3. Визначає offset і довжину тіла, якщо є Content-Length.

Усе. Після того, як parse_msg() повернувся, ви знаєте, що це INVITE на sip:bob@example.com, — але якщо запитаєте «який URI у To?», отримаєте NULL, поки щось явно його не розпарсить.

Це не лінь — це несуча конструкція. Типовий request_route — це 30 рядків скрипта, що чіпають $ru, $tu, можливо $hdr(Authorization), і форвардять. Парсити повністю кожен заголовок на кожному повідомленні — Via, Record-Route, Contact, Allow, Supported, User-Agent, ще десятки — це коштувало б на порядок більше CPU per message, без жодної користі.

Парсинг на вимогу

Коли скрипту (чи функції модуля) потрібен конкретний заголовок, він дзвонить одну з entry point'ів парсера:

parse_headers(msg, HDR_TO_F, 0);    // розпарсити аж до To включно
parse_to(msg);                       // розпарсити значення To у addr_body
parse_uri(uri.s, uri.len, &parsed); // розпарсити URI-строку в struct sip_uri
parse_body(msg);                     // розпарсити SDP чи інше тіло

parse_headers() приймає бітмаску того, що вам треба. Ходить по header-списку, парсить значення тих, що матчать біт у масці, поки не розпарсить усе запитане. Робота кумулятивна: розпарсити HDR_TO_F | HDR_FROM_F після того, як уже розпарсене HDR_TO_F, — це лише робота для From.

Результат кожного парсингу кешується на sip_msg. Shortcut-вказівник (msg->to, msg->from) виставляється, поле parsed структури hdr_field тримає типізоване значення (to_body, from_body і т. д.), і подальші звернення повертають миттєво.

Як виглядає «розпарсене»

hdr_field після парсингу тримає дві речі:

  • Оригінальні байти — name.s/name.len для імені, body.s/body.len для значення, обидва як str-вказівники в буфер повідомлення.
  • Типізований вказівник parsed, downcast залежно від типу — to_body* для To, via_body* для Via і т. д.

Типізована форма тримає попередньо витягнуті поля. Для To: display name, URI, tag, parameters. Для Via: protocol, host, port, branch parameter, received parameter, далі. Ці поля теж — вказівники в оригінальний буфер, не копії: tag у To — це просто пара (ptr, len) у байти, які прилетіли з мережі.

Тіло — той самий патерн. parse_body() розпізнає content-type ('application/sdp' найчастіше), парсить тіло в типізоване дерево (для SDP: sdp_info із сесіями, стрімами, media descriptions), кешує. Модулі, що чіпають тіло (rtpengine, sdpops, presence), тригерять цей парсинг.

Чому нічого не копіюється

Архітектурний вибір, що зв'язує все це: оригінальний буфер ніколи не копіюється і не модифікується під час обробки. Кожна типізована структура вказує назад на byte range'и в msg->buf. body.s заголовка — це вказівник у buf. uri.s у To — це вказівник у buf. Тіло — теж вказівник у buf.

Саме це й робить per-message-обробку дешевою:

  • Парсинг конкретного заголовка коштує O(довжина заголовка) раз, далі — O(1) на кожен доступ.
  • Загальний memory footprint — це len(message) + sizeof(чіпнутих parsed-структур), не множник від розміру повідомлення.
  • Звільнення повідомлення наприкінці route'у — одна операція: free buf, free sip_msg і все pkg-allocated під ним.

Але це й накладає обмеження на форму мутацій. Ви не можете редагувати msg->buf на місці — кожен закешований вказівник або стане stale, або муситиме перерезолвитися. Ви не можете замінити buf на модифіковану копію без інвалідизації всіх кешованих парсінгів і всіх модулів, що вже взяли вказівник.

Те, як Kamailio розв'язує це обмеження — як він дозволяє додавати, видаляти й переписувати частини повідомлення, ніколи не торкаючись буфера — і є lump-система, на яку повністю присвячений наступний розділ.


← Зміст · ← 3.1 Прийом · Далі: 3.3 Lumps →