VoIP телефонія. Asterisk. Нестандартний підхід до всього. Частина 1

Рівно рік тому до нас звернулися колишні колеги, з пропозицією взяти участь у модифікації движка VoIP оператора зв'язку. Задача зводилася до повної переробки особистого кабінету, забезпечення масштабування системи, створення системи білінгу, LCR, моніторингу видатків користувачів, контролю тривалості розмов, аналітики по дзвінках. Історія закінчилася сумно, т. к. закладений нами розширений функціонал системи нібито не відповідав ТЗ, ніяк не формализованному на папері і знаходиться тільки в головах менеджерів оператора. У зв'язку з тим, що за розроблений функціонал, який замовнику дуже сподобався, менеджери платити не захотіли, ми розірвали стосунки. NDA і договору у нас не було, тому порадившись з колегами ми вирішили частину напрацювань викласти у вільний доступ. Я думаю, що це буде серія статей. І почнемо мабуть з базових речей і архітектури.
Кожен адміністратор, який хоча б раз у житті мав справу з IP-телефонією знає, що послуги IP-телефонії можуть надаватися кінцевому абоненту декількома способами:
  • Оренда кімнати в оператора зв'язку. В даному випадку користувачеві видається особистий логін/пароль до якого "прив'язаний" телефонний номер. При використанні зв'язки логін/пароль для підключення до обладнання оператора, користувач може приймати дзвінки на орендовану номер і здійснювати дзвінки іншим абонентам з цього номера.
  • Оренда блоку номерів та підключення через SIP Trunk(транк). При підключенні через SIP Trunk, оператор зв'язку надсилає на заздалегідь узгоджений IP адреса всі дзвінки, що надходять на орендовані у цього оператора номери. Залежно від способу організації SIP транк, може використовуватися або не використовуватися парольна аутентифікація. В даному випадку встановлення правильного CallerID, який буде бачити викликається сторона при вихідних дзвінках лежить на плечах адміністратора IP АТС.
  • Купівля DID номерів у операторів зв'язку без покупки вихідного трафіку. Оператор просто перенаправляє дзвінок, що прийшов на даний номер, на вказаний сервер.
  • Купівля вихідного трафіку. Є досить багато операторів зв'язку продають вихідний трафік. При цьому в залежності від тарифного плану передається викликається стороні CallerID може передаватися або втрачатися при проходженні через ланцюжок операторів терминирующих(доставляють до кінцевого клієнта) трафік потрібного напрямку. Досить часті випадки, коли дзвінок на московський номер через московського оператора може прийти скажімо з лондонського кімнати або з бельгійського, бо маршрут був дешевше.
Компанії мають представництва в кількох країнах або бажають отримати присутність в іншій країні, можуть купити, для зручності своїх клієнтів, телефонний номер, скажімо, у Великобританії і обробляти надходять дзвінки в Москві. При цьому оператор надав їм такий номер може не займатися доставкою дзвінків у Великобританію. Деякі країни, наприклад республіка Білорусь, не надають номери нерезидентам.
Отже, виходимо з наступних початкових вимог:
  1. Сервер для прийому викликів може стояти де завгодно
  2. Кількість серверів не обмежено
  3. Кількість операторів зв'язку не обмежено
  4. Прийом виклику може відбуватися при будь-якому типі оренди номери (логін/пароль або транк)
  5. Дзвінок з певним CallerID повинен відбуватися тільки з того сервера і тільки з облікового запису, до якої на даний момент прив'язаний цей CallerID, або через оператора дозволяє міняти CallerID
  6. Дзвінки між сусідніми серверами повинні проходити прозоро без втрати інформації про абонента що викликається. Наприклад переадресація лондонського номери на внутрішній, мобільний або стаціонарний телефон в іншому регіоні
  7. Для вихідних дзвінків, в залежності від тарифного плану, повинен вибиратися найбільш дешевий маршрут з резервуванням через більш дорогі. В цілях контролю якості зв'язку необхідно забезпечити моніторинг ABR, ASR(статистичні параметри, що визначають якість зв'язку в заданому напрямку через певний вузол телефонії)
  8. Якщо є зовнішні користувачі використовують систему, що вони повинні бачити в realtime свій поточний баланс і правил на даний момент розмови
  9. При досягненні значення поточного балансу менше певної суми користувач повинен отримувати голосове повідомлення про негативному балансі з припиненням розмови
  10. Користувач повинен мати можливість здійснювати дзвінки через веб-браузер(WebRTC)
  11. Сервера обробки вхідних і вихідних дзвінків, а також сервера обслуговують абонентів можуть бути розділені
  12. Для аналітики і маршрутизації виклику потрібна геолокація вхідних і вихідних дзвінків
Невелика ремарка: описувана конфігурація є універсальною, але більшою мірою підходить для сервісів мають клієнто-орієнтований напрям, наприклад call центрів, у випадках коли необхідна персоналізація клієнтів і прив'язка клієнта до одного менеджера або групу менеджерів. Більшість описуваних механізмів універсальне і може ефективно використовуватися в інших конфігураціях.
Вимог досить багато, так з чого почати? Приймати і здійснювати дзвінки буде Asterisk, підготовкою викликів буде займатися Application Server на python, всі робочі дані ми будемо зберігати в MariaDB, а більшість логіки буде реалізовано у вигляді процедур. Це дозволить нам максимально дистанціювати логіку роботи asterisk від зайвих правил диалплане і забезпечити масштабованість спільно з уніфікацією конфігурації.
Трафік
Визначимося з нашим трафіком. Нам необхідно присутність скажімо, на Кіпрі, Америці, Великобританії та Росії. З точки зору Російського трафіку вигідніше працювати через Російських операторів зв'язку. Наприклад Весткол, IPPort та інших. Російські(московські) оператори також за досить прийнятною ціною надають в оренду телефонні номери в кодах 495 і 499. Присутність на Кіпрі, в Америці і Великобританії ми можемо забезпечити собі купивши номери у Zadarma, Multilel або когось іншого. Так як міжнародні виклики у Російських операторів досить дороги, то можна купити трафік у зарубіжних операторів для прикладу VoiceBuy або VoxBeam.
При підключенні до оператора зв'язку у варіанті "логін/пароль" дзвінки на нашу АТС будуть приходити з IP адреси сервера оператора, на якому відбувається реєстрація. Однак у випадку вихідних дзвінків адресу сервера може бути іншим.
При підключенні до оператора через SIP транк вхідні дзвінки можуть приходити з декількох серверів з пулу оператора, а вихідні дзвінки здійснюються за єдиним DNS імені, на якому в більшості випадків знаходиться декілька IP адрес. Як ми вже визначилися раніше, оператор може мати можливість заміни CallerID для вихідного виклику, що може регулюватися тарифним планом або договором. Заміна CallerID(підміна номера) в Росії при вихідних викликах заборонена. Із закордонними операторами справа йде набагато простіше і заміна CallerID цілком підтримується.
Створюємо базу даних.
Більшість опублікованих в даній статті даних і таблиць є витягами з ядра реальної бази даних, за винятком номерів телефонів і адрес IP
У зв'язку з тим, що у нас є мультивалютні оператори зв'язку, в таблиці операторів зв'язку повинна бути присутнім посилання на таблицю валют. Трохи пізніше ми розглянемо можливість мультивалютного перерахунку тарифів операторів.
Таблиця валют
CREATE TABLE `currency` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`iso` CHAR(3) NOT NULL DEFAULT " COLLATE 'utf8mb4_unicode_ci',
`name_en` VARCHAR(200) NOT NULL DEFAULT " COLLATE 'utf8mb4_unicode_ci',
`name_ru` VARCHAR(200) NOT NULL DEFAULT " COLLATE 'utf8mb4_unicode_ci',
`numcode` INT(3) UNSIGNED ZEROFILL NULL DEFAULT NULL COMMENT 'numcode country for',
PRIMARY KEY (`id`),
UNIQUE INDEX `iso` (`iso`)
)
COMMENT='Довідник валют, iso кодів і найменувань'
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;

currency






id iso name_en name_ru numcode 96 RUB Russian Ruble Російський рубль 643 122 USD US Dollar Долар США 840 156 EUR Euro Євро 978
Таблиця операторів
CREATE TABLE `providers` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`providername` VARCHAR(50) NULL DEFAULT '0' COLLATE 'utf8mb4_unicode_ci',
`currency_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`noncli_prefix` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`cli_prefix` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`premcli_prefix` VARCHAR(10) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`cli_allowed` ENUM('Y','N') NOT NULL DEFAULT 'N' COLLATE 'utf8mb4_unicode_ci',
`dynamic_calls` CHAR(1) NULL DEFAULT 'N' COLLATE 'utf8mb4_unicode_ci',
`append_plus` TINYINT(1) NOT NULL DEFAULT '1',
`dynamic_calls_caller_id` VARCHAR(30) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
PRIMARY KEY (`id`),
INDEX `FK_providers_currency` (`currency_id`),
CONSTRAINT `FK_providers_currency` FOREIGN KEY (`currency_id`) REFERENCES `currency` (`id`)
)
COMMENT='довідник операторів і префіксів для різних типів сервісів'
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;

providers









id providername currency_id noncli_prefix cli_prefix premcli_prefix cli_allowed dynamic_calls append_plus dynamic_calls_caller_id 3 Zadarma 96 N N 1 4 Multitel 122 N N 1 7 WestCall 96 Y N 0 74951815283 8 VoxBeam 122 0011103 0011101 0011102 Y Y 1 11 VoiceBuy 122 9991 9992 9993 Y Y 1 16 IPPort 96 N N 1
Отже. Оператори.
Зазвичай у зарубіжних операторів 2-3 тарифу за якими вони доставляють трафіку до абонента. Про відмінності CLI і NonCLI технік можна прочитати здесь.
Для вибору тарифного плану зазвичай використовується префікс (noncli_prefix, cli_prefix, premcli_prefix) вказується перед вашим номером у процесі формування рядка виклику.
Опис полів:
  • providername — найменування оператора
  • currency_id — валюта оператора
  • noncli_prefix — тариф без підтримки CLI
  • cli_prefix — тариф з підтримкою CLI
  • premcli_prefix — тариф преміального рівня
  • cli_allowed — вказує на те, що оператор дозволяє змінювати CLI для вихідних дзвінків
  • dynamic_calls — вказує, що оператор дозволяє здійснювати дзвінки по будь-яких напрямках з будь-яким CLI.
  • append_plus — додавати або не додавати "+" перед вашим номером. Частина операторів вимагає "+", частина-ні.
  • dynamic_calls_caller_id — CID для оператора за замовчуванням.
У випадку з WestCall та іншими операторами надають підключення через SIP транк, ви купуєте пул номерів та здійснюючи дзвінки можете змінювати вихідний номер на будь-який номер з даного пулу. IPPort як і інші оператори, при використанні схеми логін/пароль з єдиним купленим номером, не дозволяють змінювати вихідний номер. Оператори VoxBeam і VoiceBuy використовуються для вихідних дзвінків для будь-яких напрямків і дозволяють змінювати вихідний номер на будь-який інший. Правда тут є одне але! І полягає воно в тому, що кінцеві оператори зв'язку доставляють дзвінок до абонента, можуть відхилити виклик або поміняти номер абонента на свій, якщо викликає номер відповідає внутрішньому номеру країни або місцевості. Тобто скажімо при дзвінку з Росії в Україну(Київ) ми змінюємо CallerID на український номер у Києві та оператор зв'язку через якого йде виклик може цей дзвінок просто "відбити", оскільки він не відповідає внутрішній політиці обробки дзвінків "Дзвінок через зовнішній вхідний міжнародний транк не може містити внутрішніх номерів". Оператори Zadarma і Multitel у нас будуть використовуватися в якості постачальників номерів та вихідний трафік ми через них відправляти не будемо, хоча при бажанні могли б.
Важливим аспектом обробки виклику є безпека. Досить часто зустрічаються ситуації, коли невірно налаштований сервер IP-телефонії дозволяє пропускати крізь себе чужі дзвінки. Для забезпечення безпеки контурів обробляють вхідні та вихідні дзвінки, додатково до розділення операторів з різних контекстів, варто створити базу IP адрес операторів зв'язку надають нам послуги зв'язку. По-перше, це дозволить налаштувати експорт адрес firewall і прибрати зайві спроби "продзвону" через нас, по друге дасть більш повну інформацію про способи підключення до операторів зв'язку.
Таблиця IP адрес операторів зв'язку
CREATE TABLE `providers_ips` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`provider_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
`ipaddress` INT(11) UNSIGNED NOT NULL DEFAULT '2130706433',
`domainname` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`direction` ENUM('IN','OUT','IN/OUT') NOT NULL DEFAULT 'IN' COLLATE 'utf8mb4_unicode_ci',
`proto` ENUM('SIP','IAX2','H323') NOT NULL DEFAULT 'SIP' COLLATE 'utf8mb4_unicode_ci',
PRIMARY KEY (`id`),
INDEX `FK_providers_ips_providers` (`provider_id`),
CONSTRAINT `FK_providers_ips_providers` FOREIGN KEY (`provider_id`) REFERENCES `providers` (`id`)
)
COMMENT='Список адрес операторів зв'язку, використовуваних в роботі. direction вказує напрямок для вхідних/вихідних дзвінків'
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;

providers_ips
















id provider_id ipaddress domainname direction proto 1 8 2130706433 sbc.voxbeam.com OUT SIP 2 8 1607694320 95.211.119.240 IN SIP 7 3 2130706433 sip.zadarma.com OUT SIP 16 3 3106773121 proxy-1.fr.zadarma.com IN SIP 17 3 3106773122 proxy-2.fr.zadarma.com IN SIP 31 16 1506852360 89.208.190.8 IN SIP 33 16 1506852357 89.208.190.5 IN SIP 34 16 1506852354 sip.n1.ipport.net OUT SIP 35 11 2991415097 sip.voicebuy.com OUT SIP 42 4 3514573416 209.124.34.104 OUT SIP 44 7 3277775106 195.94.225.2 IN/OUT SIP 51 4 3514573417 209.124.34.105 IN SIP 52 4 3514573446 209.124.34.134 IN SIP
Опис полів:
  • provider_id вказує прив'язку адреси до оператора з таблиці providers
  • ipaddress є Int поданням fqdn адреси сервера оператора для конвертації за допомогою вбудованої функції INET_NTOA.
  • domainname вказує fqdn ім'я сервера оператора здійснює обробку виклику.
  • direction вказує напрям виклику для вказаної адреси IP(OUT тільки на вихід, IN тільки на вхід, IN/OUT і на вхід і на вихід)
  • proto вказує протокол за яким відбувається взаємодія з оператором зв'язку(SIP/IAX2/H323)
Таким чином зв'язка оператора та адрес буде виглядати наступним чином.
vw_providers




















id providername proto aa direction 16 IPPort SIP 89.208.190.5 [89.208.190.5] IN 16 IPPort SIP sip.n1.ipport.net [89.208.190.2] OUT 16 IPPort SIP 89.208.190.8 [89.208.190.8] IN 4 Multitel SIP 80.97.55.105 [80.97.55.105] IN 4 Multitel SIP 209.124.34.104 [209.124.34.104] OUT 4 Multitel SIP 41.218.96.199 [41.218.96.199] IN 11 VoiceBuy SIP sip.voicebuy.com [178.77.95.57] OUT 8 VoxBeam SIP sbc.voxbeam.com [127.0.0.1] OUT 8 VoxBeam SIP 95.211.119.240 [95.211.119.240] IN 7 WestCall SIP 195.94.225.2 [195.94.225.2] IN/OUT 3 Zadarma SIP proxy-1.fr.zadarma.com [185.45.152.129] IN 3 Zadarma SIP proxy-3.ri.zadarma.com [195.122.19.11] IN 3 Zadarma SIP siplv.zadarma.com [195.122.19.17] IN 3 Zadarma SIP proxy-8.fr.zadarma.com [185.45.152.136] IN 3 Zadarma SIP sip.zadarma.com [127.0.0.1] OUT 3 Zadarma SIP siplv1.zadarma.com [195.122.19.17] IN 3 Zadarma SIP mediarelay-1.zadarma.com [185.45.152.162] IN
Для чого це потрібно ?
  1. Вхідний дзвінок на номер телефону, орендованого нами у конкретного оператора, повинен приходити з пулу IP адрес серверів цього оператора. Тому в момент дзвінка варто перевірити номер прив'язаний до оператора з таблицею IP адрес оператора
  2. Оператор може мати декілька серверів оброблювальних вихідні дзвінки від нашого сервера, тому це потрібно врахувати при формуванні черзі дозвону
  3. Дана схема дозволяє миттєво отримувати рядок виклику. При цьому заміна domainname на ім'я транка визначеного в sip.conf або iax.conf не порушує роботу системи
  4. Номери можуть бути "розмазані" з декількох серверів для відмовостійкості, а можуть бути жорстко закріплені за кожним сервером. Немає сенсу обслуговувати виклик на сервері, до якого цей номер не прикріплений.
Спробуємо отримати рядок для виклику номера 74957777777.
Запит рядка виклику
SELECT CONCAT_WS('/',
pips.proto,
pips.domainname, CONCAT(IF(p.append_plus IS TRUE,'+',"), IFNULL(p.cli_prefix,"),74957777777)) AS dial_string
FROM providers_ips AS pips
LEFT JOIN providers AS p ON p.id=pips.provider_id
WHERE pips.direction IN ('OUT', 'IN/OUT')

Запит рядка дозвону






dial_string SIP/sip.zadarma.com/74957777777 SIP/westcall/74957777777 SIP/sbc.voxbeam.com/001110174957777777 SIP/sip.voicebuy.com/999274957777777 SIP/sip.n1.ipport.net/+74957777777
Як ми бачимо, ми отримали готові дані для дозвону, які потрібно передати додатком Dial сервера Asterisk. На цьому поки що зупинимося.
Для прив'язки орендованих у оператора зв'язку номерів, нам необхідно задати список серверів на яких ці номери будуть обслуговуватися.
Таблиця серверів
CREATE TABLE `servers` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`servername` VARCHAR(50) NULL DEFAULT '0' COLLATE 'utf8mb4_unicode_ci',
`location` INT(11) NOT NULL DEFAULT '0' COMMENT 'country code where server is located',
`ipaddress` INT(11) NOT NULL DEFAULT '2130706433' COMMENT 'localhost by default',
`comment` VARCHAR(50) NULL DEFAULT '0' COLLATE 'utf8mb4_unicode_ci',
`sip` VARCHAR(50) NULL DEFAULT NULL COMMENT 'SIP URI' COLLATE 'utf8mb4_unicode_ci',
`iax2` VARCHAR(50) NULL DEFAULT NULL COMMENT 'IAX2 address and user binding if rsa used keys' COLLATE 'utf8mb4_unicode_ci',
`iax2control` VARCHAR(50) NULL DEFAULT NULL COMMENT 'IAX2 address and control user binding for pair and route control' COLLATE 'utf8mb4_unicode_ci',
`protocol` ENUM('SIP','IAX2') NOT NULL DEFAULT 'SIP' COLLATE 'utf8mb4_unicode_ci',
PRIMARY KEY (`id`)
)
COMMENT='Інформація про сервери системи'
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;

servers







id servername location ipaddress comment sip iax2 iax2control protocol 0 undefined 0 2130706433 0 SIP 1 asterisk-macomnet 189 3557129729 0 SIP/212.5.126.1 IAX2/macomnet@macomnet IAX2/macomnetcontrol@macomnet IAX2 2 asterisk-msm 189 1506807809 0 SIP/89.208.16.1 IAX2/msm@msm IAX2/msmcontrol@msm IAX2 5 asterisk-corbina 189 1399234817 0 SIP/83.102.161.1 IAX2/corbina@corbina IAX2/corbinacontrol@corbina IAX2
Опис полів:
  • servername — внутрішнє ім'я сервера
  • location — географічна зона в якій знаходиться сервер. В даному випадку Росія
  • ipaddress — реальний IP адреса сервера
  • sip — SIP URL для з'єднання з сервером
  • iax2 — Зв'язка ключа RSA з ідентифікатором сервера для переадресації дзвінків між серверами
  • iax2control — спеціальний канал команд для віддаленого управління серверами і межсерверного взаємодії. Довжина команди до 80 символів
  • protocol — інформаційне поле для сортування за типом межсерверной зв'язку
Для чого потрібна ця таблиця і чому вона така дивна? Для межсерверного з'єднання серверів asterisk найзручніше використовувати протокол IAX. Причин кілька:
  1. весь транзитний трафік між серверами можна зашифрувати
  2. на відміну від SIP протоколу — більш просте проходження через Firewall операторів зв'язку(для роботи потрібен лише 1 UDP порт)
  3. неможливість встановлення з'єднання зловмисником з сервером без наявності ключів шифрування
  4. можливість передавати дані між серверами. наприклад сесійні змінні. У протоколі SIP можна додавати дані в заголовки сесійних пакетів, але найчастіше зайві заголовки просто "вичищаються" проміжними серверами з пакетів. У IAX протоколі можна перед встановленням з'єднання передати необхідні змінні в потоці
  5. чітка прив'язка хоста і зв'язки користувачів/ключів до контексту на сервері
приблизний конфіг iax.conf на одному з серверів
[macomnet]
type=user
username=macomnet
auth=rsa
inkeys=asterisk-corbina:asterisk-msm
context=incoming_dialer
encryption=yes
qualify=yes
disallow=all
allow=gsm
allow=ulaw

[msm]
type=peer
host=89.208.16.1
username=msm
auth=rsa
outkey=asterisk-macomnet
encryption=yes
qualify=yes
disallow=all
allow=gsm
allow=ulaw
trunk=yes

[corbina]
type=peer
host=83.102.161.1
username=corbina
auth=rsa
outkey=asterisk-macomnet
encryption=yes
qualify=yes
disallow=all
allow=gsm
allow=ulaw
trunk=yes

З серверами на початковому етапі розібралися. Тепер необхідно зробити прив'язку орендованих номерів до певного серверу. Це необхідно для коректної маршрутизації викликів.
Таблиця номерівCREATE TABLE
numbers_pool
(
id
INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
number
VARCHAR(50) NULL DEFAULT '0' COMMENT 'що Орендується номер' COLLATE 'utf8mb4_unicode_ci',
provider_id
INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Прикріплення номера до оператора',
server_id
INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Прикріплення номера до сервера',
enabled
BIT(1) NOT NULL DEFAULT b'1' COMMENT 'Включення/відключення номера',
direction
ENUM('IN','OUT','IN/OUT') NULL DEFAULT 'IN' COMMENT 'Напрямок дзвінка за замовчуванням' COLLATE 'utf8mb4_unicode_ci',
virtualflag
BIT(1) NOT NULL DEFAULT b'1' COMMENT 'Віртуальний або реальний номер',
echotest
TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Тестування номери 0/1 — вимкнено/включено',
PRIMARY KEY (
id
),
UNIQUE INDEX
number
(
number
),
INDEX
FK_number_assignment_copy_providers
(
provider_id
),
INDEX
FK_number_assignment_servers
(
server_id
),
INDEX
number btree
(
number
),
CONSTRAINT
FK_provider
FOREIGN KEY (
provider_id
) REFERENCES
providers
(
id
),
CONSTRAINT
FK_server
FOREIGN KEY (
server_id
) REFERENCES
servers
(
id
)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;
numbers_pool















id number provider_id server_id enabled direction virtualflag echotest 125 37167859001 4 1 1 IN 1 0 126 37167859002 4 1 1 IN 1 0 278 4971122954000 4 1 0 IN 1 0 279 74951815000 7 1 1 IN/OUT 0 0 280 74951815001 7 1 1 IN/OUT 0 0 281 74951815002 7 1 1 IN/OUT 0 0 426 74951339501 3 2 0 IN/OUT 1 0 427 74951339502 3 1 0 IN/OUT 1 0 515 74957952301 16 1 1 IN/OUT 0 0 516 74957952302 16 2 1 IN/OUT 0 0 529 442038070121 11 1 1 IN 1 0 531 442038070123 11 1 1 IN 1 0
Опис полів:
  • number — номер у міжнародному форматі
  • provider_id — прив'язка номера до оператора
  • server_id — прив'язка номера до сервера
  • enabled — обслуговувати виклики на даному номері чи ні
  • direction — допустимі номером напрямки виклику
  • virtualflag — номер є "реальним" або "онлайн". "Реальні" номери прив'язані до диалпирам, "віртуальні" не мають прив'язки
  • echotest — включення режиму тестування номери. якщо режим включений, то виклик переадресується на додаток Echo() сервера Asterisk
На момент розробки системи кількість орендованих номерів було близько 400 штук, кількість серверів 4. Для тестування маршрутизації викликів навіть додавали Мультіфон від Мегафона. При використанні "реальних" номерів, прив'язаних до логіну/паролю, необхідно забезпечити здійснення виклику через правильний диалпир. Так як asterisk не може знати через який диалпир повинен маршрутизироваться виклик, необхідно створити таблицю зв'язків.
Таблиця зв'язків номерів та dialpeer
CREATE TABLE `master` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`numbers_pool_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
`serverid` INT(11) UNSIGNED NOT NULL DEFAULT '0',
`gatename` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`gatenumber` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`cid_support` ENUM('Y','N') NULL DEFAULT 'Y' COLLATE 'utf8mb4_unicode_ci',
`contextname` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`comment` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
PRIMARY KEY (`id`),
UNIQUE INDEX `name_number` (`gatename`, `gatenumber`),
INDEX `FK_gates_servers` (`serverid`),
INDEX `FK_gates_numbers_pool` (`numbers_pool_id`),
CONSTRAINT `FK_gates_numbers_pool` FOREIGN KEY (`numbers_pool_id`) REFERENCES `numbers_pool` (`id`),
CONSTRAINT `FK_gates_servers` FOREIGN KEY (`serverid`) REFERENCES `servers` (`id`)
)
COMMENT='List of gates on servers'
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;

gates








id numbers_pool_id serverid gatename gatenumber cid_support contextname comment 1 516 1 sip_peer_msm_2302 74957952302 Y sip_msm_74957952302 2 515 1 sip_peer_msm_2301 74957952301 Y sip_msm_74957952301 8 279 1 westcall 74951815000 Y westcall 9 280 1 westcall 74951815001 Y westcall 10 281 1 westcall 74951815002 Y westcall
Опис полів:
  • numbers_pool_id — ідентифікатор номери з таблиці numbers_pool
  • serverid — прив'язка гейту до сервера
  • gatename — ім'я гейта
  • gatenumber — номер підключений до гейту
  • cid_support — чи підтримується встановлення callerid при вихідному виклику
  • contextname — контекст на сервері, який обслуговує вихідні дзвінки за цим номером
На цьому налаштування бази для обслуговування викликів практично закінчена. Я навмисно не розглядаю зараз роботу тарифікатора і систем контролю виклику, так як це дуже велика тема, яку варто винести в окрему статтю.
У зв'язку з тим, що основна логіка обробки і маршрутизації виклику винесена з Asterisk Application Server, а процедури знаходяться у MariaDB, необхідно забезпечити механізми налагодження і контролю роботи процедур. Для цього можна використовувати наступну схему.
Для зберігання налагоджувальної інформації створюємо додаткову базу даних debug і створюємо там таблицю debug_records
CREATE TABLE `debug_records` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`logtime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`procedure_name` VARCHAR(50) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`debug_text` TEXT NOT NULL COLLATE 'utf8mb4_unicode_ci',
PRIMARY KEY (`id`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;

Для запису в таблицю даних створюємо процедуру debug
CREATE DEFINER=`root`@`localhost` PROCEDURE `debug`(
IN `ProcedureName` VARCHAR(50),
IN `DebugText` TEXT
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'Save debug info to log'
BEGIN
DECLARE DebugEnabled TINYINT DEFAULT False;
SET DebugEnabled=True;
IF THEN DebugEnabled
INSERT INTO debug_records (procedure_name,debug_text) VALUES (ProcedureName,DebugText);
COMMIT;
END IF;
END

Тепер налагодження і контроль роботи процедури на етапі розробки перетворюється в задоволення. На початку кожної процедури ми визначаємо дві змінні, перша — це назва процедури, друга — це прапор визначає включена налагодження чи ні. І далі в різних місцях, де процедура може повести себе некоректно — попередньо додаємо запис у налагоджувальний журнал.
CREATE DEFINER=`root`@`localhost` PROCEDURE `usp_gettypeofcall`(
IN `IncomingPhoneNumber` VARCHAR(30),
IN `TargetGateNumber` VARCHAR(30),
IN `CHANID` VARCHAR(60),
IN `SYSTEMNAME` VARCHAR(30)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'Отримання типу дзвінка і маршруту на основі вхідних номерів'
BEGIN
DECLARE ProcedureName VARCHAR(50) DEFAULT 'usp_gettypeofcall';
DECLARE ProcedureDebug TINYINT DEFAULT True;
IF ProcedureDebug THEN CALL debug.debug(ProcedureName,CONCAT_WS(' ','Call from ',IncomingPhoneNumber,'to',TargetGateNumber,'server at',SYSTEMNAME,'with channelid',CHANID)); END IF;
END

Звертаю Вашу увагу на те, що використання даної логіки не рекомендовано!!! у продакшн режимі на великих навантаженнях, так як налагоджувальну інформацію може сильно знизити швидкодію.
Налаштування Asterisk
Перейдемо до налаштування сервера Asterisk. Стандартна процедура обробки викликів потрапляють в контекст public, в більшості існуючих конфігурацій, виглядає наступним чином
[public]
розширеннями => 74951815000,1,NoOp(Incoming Call to ${РОЗШИРЕННЯМИ})
same => n,Dial(SIP/1000,60)
same => n,Hangup()

обробка вихідного дзвінка з контексту dialout виглядає так
[dialout]
розширеннями => _X.,1,NoOp(Outgoing Call to ${РОЗШИРЕННЯМИ})
same => n,Dial(SIP/dialpeer_name/${РОЗШИРЕННЯМИ},60)
same => n,Hangup()
або
[dialout]
розширеннями => _X.,1,NoOp(Outgoing Call to ${РОЗШИРЕННЯМИ})
same => n,Dial(SIP/sip.n1.ipport.net/${РОЗШИРЕННЯМИ},60)
same => n,Hangup()
або
[dialout]
розширеннями => _X.,1,NoOp(Outgoing Call to ${РОЗШИРЕННЯМИ})
same => n,Dial(SIP/${РОЗШИРЕННЯМИ}@sip.n1.ipport.net,60)
same => n,Hangup()

В загальному хто як звик, і кому як подобається. Як ми бачимо при такій організації вихідних дзвінків практично немає інформації про стан виклику. Ми модифікували Dialplan обробки дзвінків та додали до нього сервісні функції
[service]
; GetIP subroutine
розширеннями => getip,1,Set(TESTAT=${CUT(SIP_HEADER(From),@,2)})
same => n,GotoIf($["${TESTAT}" != ""]?hasat)
same => n,Set(FROM_IP=${CUT(CUT(SIP_HEADER(From),>,1),:,2)})
same => n,Goto(gotip)
same => 20(hasat),Set(FROM_IP=${CUT(ВИРІЗАТИ(CUT(SIP_HEADER(From),@,2),>,1),:,1)})
same => n(gotip),NoOp(Incoming Server IP is ${FROM_IP})
same => n Return()

розширеннями => set_handler,1,Set(CHANNEL(hangup_handler_push)=service,outbound_handler,1)
same => n,AGI(/usr/local/etc/asterisk_scripts/create_channel_record.py)
same => n Return()

; Set Hangup handler for channel
розширеннями => outbound_handler,1,NoOp(Hungup handler python started)
same => n,AGI(/usr/local/etc/asterisk_scripts/hangup.py)
same => n,HangupCauseClear()
same => n Return()

розширеннями => no_more_paths,1,NoOp(No more dial paths)
same => n,Hangup()

[predial]
розширеннями => s,1,NoOp(PreDial handler python started)
same => n,AGI(/usr/local/etc/asterisk_scripts/predial.py)
same => n Return()

[public]
розширеннями => _X.,1,GoSub(service,getip,1)
same => n,AGI(/usr/local/etc/asterisk_scripts/incoming.py)

розширеннями => _+X.,1,GoSub(service,getip,1)
same => n,AGI(/usr/local/etc/asterisk_scripts/incoming.py)

[users_context]
розширеннями => _X.,1,NoOp()
same => n,AGI(/usr/local/etc/asterisk_scripts/make_a_route.py)

[make_a_call]
розширеннями => h,1,NoOp(Hangup)

розширеннями => _.,1,NoOp(${РОЗШИРЕННЯМИ})
same => n,SET(__LoopCount=1)
same => n(try),AGI(/usr/local/etc/asterisk_scripts/incoming_dialer.py)
same => n,Dial(${DIALSTRING},60,b(service^set_handler^1)U(predial))
same => n,SET(__LoopCount=${IF($[${HANGUPCAUSE}=17]?10:${LoopCount})})
same => n,Set(__LoopCount=${INC(LoopCount)})
same => n,NoOp(Current LoopCount ${LoopCount})
same => n,GotoIf($["${LoopCount}" < 10]?try)
same => n,Hangup()

[redirect]
розширеннями => h,1,NoOp(Hangup)

розширеннями => _.,1,NoOp(${ForwardPath})
same => n,Dial(${ForwardPath}/${РОЗШИРЕННЯМИ},60,b(service^set_handler^1))
same => n,Hangup()

Контекст [service] містить в собі сервісні функції, такі як:
  • getip — визначення IP адреси вхідного сервера
  • set_handler — установка обробників на поточний вихідний дзвінок. Даний обробник викликається перед здійсненням виклику при створенні каналу
  • outbound_handler — обробник завершення. Викликається у всіх випадках відбою з боку абонента
  • no_more_paths — використовується, якщо вичерпані всі доступні варіанти виклику абонента
Контекст [predial] викликається перед з'єднанням абонентів при успішному піднятті трубки на викликається стороні.
В контекст [public] потрапляють всі зовнішні дзвінки.
В контекст [redirect] потрапляють всі дзвінки, які необхідно переадресувати з сервера на сервер.
Контекст [users_context] є основним контекстом користувачів для підготовки маршрутів для дзвінків.
Контекст [make_a_call] є основним контекстом при здійсненні вихідного дзвінка.
Розглянемо більш детально процедуру обробки вхідного виклику:
  1. Виклик надходить з сервера оператора і потрапляє в контекст [public]
  2. Викликається процедура визначення IP адреси сервера, з якого прийшов виклик на основі заголовків SIP пакета
  3. Викликається AGI додаток incoming.py здійснює початкову обробку вхідного дзвінка.
  4. Внутрішня логіка програми підключається до бази даних і відправляє в процедуру перевірки підключень початкові наявні параметри: IP адреса сервера, або номер, код сервера. Код сервера є унікальним, відповідає таблиці серверів і визначається у файлі asterisk.conf.
  5. Якщо з ідентифікацією номери все нормально, то проводиться пошук раніше використаних дзвінків користувачів абоненту. У разі успішного пошуку виклик переадресується на ініціатора попереднього дзвінка. У випадку невдалого пошуку виклик переадресується на чергового оператора. Якщо з ідентифікацією номери виникли проблеми, то проводиться голосова "інтервал".
Всі внутрішні і зовнішні вихідні дзвінки відбуваються через контексти [users_context] і [make_a_call].
Розглянемо докладніше процедуру вихідного виклику:
  1. Внутрішній абонент набирає номер абонента
  2. Виклик потрапляє в контекст [users_context]
  3. Вихідні дані для здійснення виклику передаються в AGI додаток make_a_route.py, внутрішня логіка якого, використовуючи дані бази даних, формує чергу маршрутів для здійснення дзвінка. На основі отриманого списку маршрутів, який складається з:
    • оператора через якого відбувається виклик
    • вихідного диалпира для здійснення виклику
    • CallerID для вихідного виклику
    • вартості виклику через оператора

    • додаток make_a_route.py змінює контекст [make_a_call] або [redirect] і передає в них ідентифікатор сформованої черги
  4. Контекст [redirect] використовується у випадках, коли жорстко прив'язаний до сервера номер, з якого необхідно зробити виклик, знаходиться на іншому сервері.
  5. Контекст [make_a_call] представляє собою цикл дозвону з моніторингом стану. В нашій конфігурації ми обмежуємо кількість ітерацій в циклі дозвону до 10 штук. Тому при зверненні в контекст дозвону, ми встановлюємо в 1 внутрішню змінну ітерацій дозвону, що діє тільки в поточній сесії.
  6. Дані черзі отримані з попереднього контексту і номер поточної ітерації дозвону передаються в AGI додаток incoming_dialer.py, яке повертає сформований заздалегідь, AGI додатку make_a_route.py, DialString для здійснення дзвінка.
  7. Для здійснення моніторингу виклику, використовуються додаткові опції програми Бізнес, а саме U і b. Опція b дозволяє викликати підпрограми після створення каналу зв'язку, але перед самим викликів. В даному випадку ця опція використовується для установки AGI додатка, яке буде викликано при розриві з'єднання. Одночасно з викликом даної опції, в базу даних заноситься інформація про поточний здійснюється виклик. Опція U дозволяє викликати підпрограми при встановленні з'єднання з абонентом, тобто в момент підняття викликуваним абонентом трубки. Це дозволяє гарантовано відстежити початок розмови і почати відлік часу.
  8. Якщо з'єднання з абонентом не вдалося і код повернення не дорівнює 17(Зайнято), слід нова ітерація циклу.
  9. Вся інформація про завершення з'єднання через поточний канал зв'язку заноситься в базу даних. Що дозволяє вважати ABR, ASR та іншу аналітику шляхом звичайного запиту до бази даних
  10. Якщо кількість циклів дозвону перевищило наявне в базі даних, то відбувається перехід в контекст [no_more_paths], який може здійснювати інформування клієнта про неможливість з'єднання з абонентом
На цьому логіка роботи сервера Asterisk закінчується. Використовуючи дану зв'язку бази даних і додатків, додавання серверів в обслуговуючий кластер відбувається шляхом копіювання конфігураційної директорії сервера Asterisk, зміною імені сервера, генерацією нових ключів RSA для IAX з'єднань і прописуванням ідентифікаційних даних сервера в одній таблиці. Вся інша логіка роботи формується через веб інтерфейс шляхом прив'язки номерів та операторів до серверів.
Використання AGI програм на Python обумовлено усталеністю середовища розробки при проектуванні Application сервера. Початкова версія AGI додатків використовувала пряме підключення до бази. Після запуску системи і налагодження роботи ядра, AGI програми були переписані під використання протоколу HTTP і Application Server uwsgi. Частина операцій, що не потребують перевірки умов і обчислень, наприклад перевірка категорій номерів, була переписана для швидкості на використання CURL в диалплане.
стаття присвячена тарификатору, LCR і визначення геопозиции викликаються або абонентів.
© Aborche 2017
Aborche
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.