Самый верный способ сэкономить на звонках – использовать IP-телефонию. Но, к сожалению, даже если установить на мобильный телефон какой-нибудь Skype-клиент, ты сможешь использовать его только при наличии Wi-Fi или 3G. Чтобы обойти эту привязанность к интернету, можно организовать систему callback, которая будет звонить обоим абонентам по VoIP и связывать их между собой. В этом случае нет необходимости ни в интернете, ни в продвинутом телефоне, ни даже в VoIP-клиенте.

Система callback существует давно и предлагается многими VoIP-компаниями. Можно зайти на сайт, ввести два номера телефона, и специальный сервис позвонит обоим абонентам, чтобы соединить их между собой. Я даже пользовался когда-то этим с мобильного телефона, открывая страницу такого сервиса в веб-браузере. Этим можно воспользоваться разок где-нибудь за границей в роуминге, но использовать постоянно – нет. Совсем другое дело – организовать удобный сервис саму. Схема такая: ты звонишь на определенный номер, где установлен специальный сервер, тот сбрасывает звонок и сам перезванивает. Тебе остается набрать специальный PIN-код (выполнить авторизацию) и номер для звонка, после чего дожидаться соединения. Это называется обратным звонком, или callback’ом. Штука удобная и довольно простая в организации.

 

Мини-АТС

Если при слове «Мини-АТС» у тебя возникает ассоциация с жутко дорогостоящим оборудованием, которое устанавливается в офисах, оно ошибочно. Помимо аппаратных АТС’ок, огромное распространение получили программные продукты, в том числе бесплатный сервер Asterisk. Собственно, Asterisk будет сердцем нашей системы. Про базовую установку сервера и первичную настройку, чтобы все заработало, у нас были две статьи. PDF-версии ты найдешь на диске, а также можешь прочитать их на сайте (www.xakep.ru/magazine/xa/107/152/1.asp и www.xakep.ru/magazine/xa/108/154/1.asp). Я не рекомендую использовать для наших целей сборки типа TrixBox, Elastix и т.д.; проще будет установить и настроить все вручную. Но повторять то, о чем мы писали отдельные статьи, я сейчас не буду.

Итак, предположим, что Asterisk у нас есть. Первое, что нужно сделать – это купить и зарегистрировать на Asterisk местный городской номер, который отдается по SIP. На него мы будем звонить. Можно, конечно, не покупать SIP-номер, а использовать обычный аналоговый, который приходит по меди. Но тогда придется докупить VoIP-шлюз с FXO-портом, а с ним могут возникнуть проблемы: на древних и не очень древних АТС не всегда работает определение Caller ID, которое нам очень нужно. Да и вообще, дополнительное звено в цепочке только понизит надежность системы. По этой причине SIP-номер, безусловно, предпочтительнее. Следующий шаг – покупка (и настройка) аккаунта у VoIP-провайдера, через который мы будем звонить. Можно купить несколько и при звонках за рубеж использовать один, в Москву – другой, по России – третий. Выбор большой.

Еще пара замечаний. В качестве номера можно использовать свой сотовый номер, только для подключения его к Asterisk потребуется VoIP-GSM шлюз, а они стоят дорого: примерно от 5000 рублей за порт. Есть обходной путь – использовать для этого 3G-модем, стоимость которого не превышает 1000 рублей. Для сотового телефона желательно прикупить любой SIM Dialer, который позволит пользоваться такой схемой звонков максимально удобно – по сути, звонящему нужно будет только выбрать контакт в записной книге, а звонок и запрос к callback-системе будет произведен автоматически. Подробнее о VoIP-GSM шлюзе и SIM Dialer’е ты можешь прочитать во врезках.

 

Обработка звонков

Теперь, когда все приготовления выполнены, Asterisk настроен по инструкциям из статей, можно приступать к организации нашего сервиса. И начнем мы с того, что поменяем так называемый контекст. Для этого открываем на Астериске файл /etc/asterisk/extensions.conf (в этом файле описывается план набора, то есть как будут себя вести все входящие и исходящие вызовы) и находим контекст, в который приходят все входящие извне звонки: у меня он называется [fromgorod]. При входящем звонке система будет определять номер звонящего, и, если он есть в «списке», звонок будет отправляться на голосовое меню (IVR), в котором будет предложено набрать PIN-код, а далее – номер для звонка. Пусть мой городской номер 310309:

[fromgorod]
exten => 310309,1,NoOp(zvonyat s nomera ${CALLERID(all)})
exten => 310309,n,NoOp(${STRFTIME(${EPOCH},,%d.%m.%Y-%H:%M:%S)})
exten => 310309,n,GoToIf($["${CALLERID(number)}" = "8901234567"]?ivr,s,1)
exten => 310309,n,Answer() ;Отвечаем
......

Функция NoOp позволяет вывести в консоль Asterisk текст или состояние переменной. Первая строка выводит в консоль Caller ID звонящего, а вторая – дату и время звонка. Для работы системы это не нужно, но при отладке очень полезно. Строка «exten => 310309,n,GoToIf($["${CALLERID(number)}" = "8901234567"]?ivr,s,1)» – это неполное ветвление, оно проверяет, с какого номера пришел вызов. Если с номера 8901234567, то вызов уходит в контекст IVR; если же номер другой, тогда обработка вызова пройдет по обычной схеме. Обрати внимание, что номер может приходить без 8 в начале.

Если callback-системой будет пользоваться пара человек, то прописать под каждый их номер еще одну строчку в конфиге не будет большой проблемой. Но что, если их будет 50? Изящнее всего прописать всех пользователей в специальной базе данных. Вместе с Asterisk часто используют MySQL, чтобы записывать в нее логи звонков – CDR. В результате на сервере создается база Asterisk, в которой есть таблица CDR. Мы в этой базе создадим еще одну таблицу – callback. Для этого в консоли набираем «mysql -u asterisk -p asterisk», далее указываем пользователя, таблицу и запрос на ввод пароля. После ввода пароля создаем таблицу (телефон, PIN-код, переменная callback, имя) и заполняем параметрами одного из пользователей:

CREATE TABLE `callback` (
`phone` varchar(80) NOT NULL default '',
`pin` int(11) NOT NULL default '4321',
`callback` int(11) NOT NULL default '0',
`user` varchar(255) NOT NULL default ''
);
INSERT INTO callback(phone, pin, user) values('8901234567', '2602', 'Aggressor');

Итак, база с данными есть, как же Asterisk узнает об этом? Все достаточно просто, нужно дополнить наш контекст [fromgorod]:

exten => 310309,1,NoOp(zvonyat s nomera ${CALLERID(all)})
exten => 310309,n,NoOp(${STRFTIME(${EPOCH},,%d.%m.%Y-%H:%M:%S)})
exten => 310309,n,MYSQL(Connect connid localhost asterisk asterisk asterisk)
exten => 310309,n,MYSQL(Query resultid ${connid} select pin, callback from callback where phone=${CALLERID(number)})
exten => 310309,n,MYSQL(Fetch fetchid ${resultid} pin callback)
exten => 310309,n,NoOp(pin -> ${pin} callback# -> ${callback})
exten => 310309,n,MYSQL(Clear ${resultid})
exten => 310309,n,MYSQL(Disconnect ${connid})
exten => 310309,n,GoToIf($["${pin}" != ""]?ivr-pass,s,1)
exten => 310309,n,Answer() ;Отвечаем

Каждая строчка, по сути, говорит сама за себя: сначала обращаемся к базе, далее с помощью SQL-запроса получаем параметры для номера звонящего абонента и обрабатываем их. Непонятной может показаться последняя строчка «GoToIf($["${pin}" != ""]?ivr-pass,s,1)». Если в результате запроса номер найдется в базе, то переменная pin будет не пустой, и тогда дальше обработка вызова пойдет в контексте ivr-pass.

 

Настраиваем IVR

Итак, номер пользователя определяется и сверяется с базой данных. Что дальше? Необходимо проиграть ему инструкции, как ввести PIN-код, чтобы произвести авторизацию пользователя, и обработать вход. Голосовые меню, которые взаимодействуют с пользователем, называются IVR. После того, как номер абонента определился, звонок перекидывается на контекст ivr-pas:

[ivr-pass]
exten => s,1,Background(WelcomePass) ;
exten =>s,n,WaitExten(10)
exten => _XXXX,1, GoToIf($["${EXTEN}" = "${pin}"]?ivr,s,1)
exten => _XXXX,n,Hangup
exten => t,1,Hangup
exten => i,1,Hangup

Здесь мы воспроизводим ролик WelcomePass (его необходимо предварительно скопировать в /var/lib/asterisk/sounds/ru). Потом ждем выбора пользователя 10 сек. Если за 10 секунд никакой номер не введен, то кладем трубку: exten => t,1,Hangup. Если введен PIN не больше четырех символов, опять же, кладем трубку: exten => i,1,Hangup. Никто не мешает, к примеру, вместо Hangup прописать возможность еще пару раз ввести PIN, и только после третьей неудачной попытки класть трубку. Так или иначе, если были введены четыре символа, которые совпадают с PIN-кодом, то мы переходим в контекст ivr.

[ivr]
exten =>s,1,Set (inum=0)
exten =>s,n,Set (tnum=0)
exten => s,n,Background(Welcome)
exten =>s,n,WaitExten(10)
exten => 1,1,GoTo(ivr-out,s,1)
exten => 2,1,GoTo(ivr-ch-pin,s,1)
exten => i,1,Playback(pbx-invalid)
exten => i,n,Set(inum=$[${inum} + 1])
exten => i,n,GotoIf($["${inum}" < "3"]?s,1)
exten => i,n,Hangup()
exten => t,1,Set(tnum=$[${tnum} + 1])
exten => t,n,GotoIf($["${tnum}" < "3"]?s,1)
exten => t,n,Hangup()

Контекст ivr начинается с обнуления двух переменных inum и tnum – это количество неверных попыток ввода и количество прошедших таймаутов. При каждом неверном вводе воспроизводится стандартный ролик pbx-invalid, а переменная inum увеличивается на 1. После трех ошибок кладется трубка, то же самое происходит и с переменной tnum. Далее воспроизводится ролик Welcome, за ним ожидаем ввод номера для звонка. В нашем меню две опции: 1 – позвонить и 2 – сменить PIN-код:

[ivr-out]
exten => s,1,Set (inum=0)
exten => s,n,Set (tnum=0)
exten => s,n,Background(beep)
exten => s,n,WaitExten(10)
exten => 89XXXXXXXXX,1,Dial(SIP/bla1/${EXTEN}
exten => 89XXXXXXXXX ,n,Hangup
exten => 8495XXXXXXX,1,Dial(SIP/bla2/${EXTEN}
exten => 8495XXXXXXX ,n,Hangup
exten => 8[2-8]XXXXXXXXX,1,Dial(SIP/blabla3/${EXTEN}
exten => 8[2-8]XXXXXXXXX ,n,Hangup
exten => i,1,Playback(pbx-invalid)
exten => i,n,Set(inum=$[${inum} + 1])
exten => i,n,GotoIf($["${inum}" < "3"]?s,1)
exten => i,n,Hangup()
exten => t,1,Set(tnum=$[${tnum} + 1])
exten => t,n,GotoIf($["${tnum}" < "3"]?s,1)
exten => t,n,Hangup()

[ivr-ch-pin]
exten => s,1,Background(beep)
exten => s,n,WaitExten(10)
exten => _XXXX,1,MYSQL(Connect connid localhost asterisk asterisk asterisk)
exten => _XXXX,n,MYSQL(Query resultid ${connid} update callback set `pin`=${EXTEN} where phone=${CALLERID(number)})
exten => _XXXX,n,MYSQL(Disconnect ${connid})
exten => _XXXX,n,Hangup()
exten => i,1,Hangup()
exten => t,1,Hangup()

В контексте ivr-out прописаны исходящие звонки. Вначале воспроизводится стандартный «бииип», после которого можно набирать номер для звонка. В конфиге у нас прописаны три направления: сотовые, Москва и межгород; каждое направление соединяется через определенный транк (аккаунт VoIP-провайдера): blabla1, blabla2 или blabla3. Можно обойтись одним, но для каждого направления можно выбрать наиболее выгодного VoIP-оператора, этим мы и воспользовались.

В контексте ivr-ch-pin, который отвечает за смену PIN’а: сначала воспроизводится «бииип», после чего дается 10 сек на ввод нового PIN’а. Когда новый PIN введен, происходит подключение к базе и обновление PIN-кода в таблице.

 

Call-файлы в Asterisk'е

Собственно, с этого момента система уже работает. Мы звоним на наш номер, авторизуемся с помощью PIN-кода, далее вводим номер телефона, на который Asterisk и перенаправляет наш звонок. Тестовый звонок… да, все работает! Однако в самом начале статьи мы говорили о том, что наша callback-система должна сама перезванивать, чтобы мы тратились на исходящие звонки с сотового. Как же это сделать?

В Астериске есть так называемые call-файлы, которые позволяют инициировать вызов и соединять два номера. Создаем конфиг и заполняем следующим:

Channel: SIP/blabla1/8901234567
MaxRetries: 2
RetryTime: 3
WaitTime: 20
Context: ivr-pass
Extension: s
Priority: 2
Archive: Yes

Что за... и за что отвечает:

  • Channel – указывает тип, название транка и номер телефона;
  • MaxRetries – параметр определяет количество попыток дозвона. Как только они будут исчерпаны, файл удалится;
  • RetryTime – время между повторениями;
  • WaitTime – этот параметр указывает, сколько времени необходимо ждать поднятия трубки до того, как прекратить попытку дозвониться;
  • Context – это контекст, выполнение которого начнется после поднятия трубки;
  • Extension – это номер в контексте ivr-pass, который будет набран, когда возьмут трубку (пишем тут s);
  • Priority – это приоритет экстеншина s, с которого начнется обработка (укажем 2)
  • Archive – если поставить Yes, тогда после выполнения call-файла в /var/spool/asterisk/outgoing_done можно будет посмотреть историю обработки вызова.

Если созданный файл переместить в /var/spool/asterisk/outgoing/, то Астериск сразу начнет звонить на номер 8901234567 (причем рекомендуется call-файл именно перемещать, а не копировать). Время каждой попытки дозвона – 20 секунд, после чего номер набирается заново, и так два раза. Если во время одной из попыток абонент возьмет трубку, то система попытается набрать экстеншен s в контексте callback.

 

Настраиваем callback

Добавить гибкости, подставляя нужный номер, можно при помощи AGI (AsteriskGatewayInterface), интерфейса взаимодействия с внешними скриптами. Внешний скрипт можно написать на Perl, PHP, C, Bash. Предлагаю написать нужный нам скрипт на Bash – это проще и быстрее всего, выглядеть он будет так:

#!/bin/bash
echo Channel: SIP/blabla1/$1 > /tmp/$2
echoMaxRetries: 2 >> /tmp/$2
echoRetryTime: 3 >> /tmp/$2
echoWaitTime: 20 >> /tmp/$2
echo Context: ivr-pass >> /tmp/$2
echo Extension: s >> /tmp/$2
echo Priority: 2 >> /tmp/$2
echo Archive: Yes >> /tmp/$2
mv /tmp/$2 /var/spool/asterisk/outgoing

Готовый файл называем callback.agi и перемещаем в /var/lib/asterisk/agi-bin. При вызове скрипта из контекста в Астериске ему будут переданы две переменных: номер телефона ($1 в скрипте), на который будем перезванивать, и имя call-файла ($2 в скрипте).

Когда мы создавали таблицу callback,то сделали в ней поле callback, которое по умолчанию равно 0. При входящих звонках мы получаем значение этого поля вмести с PIN-кодом. Если состояние этого поля не равно 0, то будем перезванивать. Отредактируем контекст ivr-pass и создадим новый callback:

[ivr-pass]
exten => s,1, GoToIf($["${callback}"! = "0"]?callback,s,1)
exten => s,n,Background(WelcomePass) ;
exten =>s,n,WaitExten(10)
exten => _XXXX,1, GoToIf($["${EXTEN}" = "${pin}"]?ivr,s,1)
exten => _XXXX,n,Hangup
exten => t,1,Hangup
exten => i,1,Hangup

[callback]
exten => s,1,AGI(callback.agi,${callback},${UNIQUEID})
exten =>s,n,hangup

Первая строка в [callback] запускает скрипт под названием callback.agi и передает две переменных: номер и UNIQUEID в качестве названия для call-файла. Таким образом и происходит обратный вызов.

 

Плюсы и минусы

Результат всех этих действий – полноценная callback-система. Ее плюсы очевидны: можно реально экономить при звонках, особенно за пределы своего города или в роуминге. Из минусов – при звонке будет теряться твой CallerID, у абонента вместо этого будет высвечиваться номер VoIP-оператора. Как вариант, можно найти такого VoIP-прова, который позволяет подставить свой (или даже произвольный) CallerID. Еще один минус системы – увеличенное время соединения. У меня при звонке через sim-dialer от момента вызова номера в контакт листе до «гудков» на набранный номер уходит примерно 20-25 секунд. Но я готов ждать :). В ближайших планах – прикрутить к системе биллинг и реализовать заказ звонка через сайт, продавая callback как услугу. 🙂

 

SimDialer

Использовать callback-систему в «голом» виде не всегда удобно. Ведь приходится вручную звонить на номер доступа, отвечать на звонок и вручную набирать PIN-код и номер для соединения. Я бы, возможно, и не стал браться за всю эту затею, если бы мой знакомый не заказал себе набор SimDialer. Он представляет собой набор из маленького чипа и контактов, которые накладываются поверх SIM-карты. После его установки в телефоне появляется новое SIM-меню. В меню указывается номер доступа к нашей системе и сам PIN-код. Важный пункт – bypassprefixes, в котором перечисляются номера телефонов, набор на которые должен осуществляться напрямую. В результате использовать callback-систему становится проще простого. Ты открываешь телефонную книгу, находишь нужный номер и просто звонишь. Если номер есть в bypassprefixes, то звонок пойдет через GSM, а если нет, то через номер доступа, причем SimDialer сам пошлет DTMF-сигналами в Астериск нужный номер и PIN-код. Увы, насколько бы тонким ни был чип, но все же не во все телефоны удастся его запихнуть. К тому же, на iPhone такая штука почему-то не заработала. В качестве альтернативы я нашел приложение CardCaller, которое предназначено для звонков по карточкам IP-телефонии. Оно умеет само вводить номер телефона и PIN-код, но, к сожалению, не поддерживает callback.

 

Настраиваем GSM-шлюз

В качестве номера доступа удобно использовать не только SIP-номер, но и обычный федеральный сотовый. Чтобы подключить сотовый номер к Астериску, нужен VoIP-GSM шлюз, для этого отлично подойдет 3G USB-модем HUAWEI Е1550, который активно продают операторы сотовой связи. С его использованием можно не только сделать традиционный callback, но и реализовать обратный вызов через SMS. Прежде чем подключить модем, нужно убедиться, что он поддерживает голос, это может определить прога MICRO-BOX HUAWEI MODEM UNLOCKER. Она же снимет привязку к определенному оператору.

Для нормальной работы нам потребуется ядро 2.6.32 и выше. Скачиваем и устанавливаем модуль для Asterisk (www.makhutov.org/svn/chan_datacard), который реализует работу с 3G-модемом. Далее проверяем, появился ли chan_datacard.so в /usr/lib/asterisk/modules. Появился? Хорошо. Руками копируем ./trunk/etc/datacard.conf в /etc/asterisk. В этом конфиге по умолчанию прописаны два устройства [datacard0] и [datacard1] – одно удаляем, оно нам не нужно. Меняем разъем, куда подключен шлюз, и контекст для него:

[datacard0]
audio=/dev/ttyUSB1
data=/dev/ttyUSB2
context=datacard-incoming
group=1
rxgain=3
txgain=3

Теперь сохраняет изменения, перезапускаем Астериск – он готов к работе. Можно прописать контекст и принимать/совершать звонки, а можно проверить баланс или отправить SMS:

CLI>datacardsms datacard0 89000000000 Hello!
CLI>datacardussd datacard0 *102#
[datacard0] Got USSD response: 'Баланс 155.49 р. Аня+Саша=любовь. Аутебя? Шли ИИмя+Имя на 5050 3р'

 

DVD

Полные версии конфигов из статьи ты найдешь на нашем диске.

Check Also

В королевстве PWN. Препарируем классику переполнения буфера в современных условиях

Сколько раз и в каких только контекстах не писали об уязвимости переполнения буфера! Однак…

Оставить мнение