четверг, 28 июля 2016 г.

Использование Event Socket Library в Freeswitch.
Различные практики.

Общие сведения.

Модуль mod_event_socket представляет собой интерфейс управления FreeSWITCH
По умолчанию прослушивается локальный интерфейс 127.0.0.1, порт 8021.
Пароль для доступа по умолчанию "ClueCon".
Модуль работает в двух режимах inbound и outbound.
В inbound режиме мы подключаемся к Freeswitch с помощью скриптов и выдаем команды / получаем события. В outbound режиме мы должны иметь скрипт-демон, к которому будет подключаться Freeswitch при обработке вызовов.

Включаем возможность работы с сокетами.

# nano /usr/local/freeswitch/conf/autoload_configs/modules.conf.xml
Следующая строчка должна быть раскомментирована:
<load module="mod_event_socket"/>
Затем открываем файл непосредственного конфигурации модуля mod_event_socket:
# nano  /usr/local/freeswitch/conf/autoload_configs/event_socket.conf.xml
Конфиг для приема всех соединений с любого интерфейса на порт 8021 с паролем ClueCon выглядит так:
<configuration name="event_socket.conf" description="Socket Client">
  <settings>
    <param name="nat-map" value="false"/>
    <param name="listen-ip" value="0.0.0.0"/>
    <param name="listen-port" value="8021"/>
    <param name="password" value="ClueCon"/>
    <!--<param name="apply-inbound-acl" value="loopback.auto"/>-->
    <!--<param name="stop-on-bind-error" value="true"/>-->
  </settings>
</configuration>
Рестартуем систему:
# systemctl restart freeswitch.service
Убеждаемся, что порт 8021 слушается системой:
# netstat -ltupn | grep freesw
tcp      0    0 0.0.0.0:8021       0.0.0.0:* LISTEN  9543/freeswitch
tcp      0    0 10.200.16.214:5060 0.0.0.0:* LISTEN  9543/freeswitch
udp      0    0 10.200.16.214:5060 0.0.0.0:*         9543/freeswitch
Не забываем в Iptables прописать дополнительные ip адреса для доступа к сокетам Freeswitch по сети.

Проверяем работу сокетов в режиме inbound с использованием скриптов на PHP.

Для проверки, что сокеты работают, создадим скрипт и запустим его:
# nano /var/www/html/callyshek.php
Содержание файла:
<?php
date_default_timezone_set('Etc/GMT-3');

 $password = "ClueCon";
 $port = "8021";
 $host = "127.0.0.1";

 function event_socket_create($host, $port, $password) {
     $fp = fsockopen($host, $port, $errno, $errdesc) 
       or die("Connection to $host failed");
     socket_set_blocking($fp,false);
     
     if ($fp) {
         while (!feof($fp)) {
            $buffer = fgets($fp, 1024);
            usleep(100); //allow time for reponse
            if (trim($buffer) == "Content-Type: auth/request") {
               fputs($fp, "auth $password\n\n");
               break;
            }
         }
         return $fp;
     }
     else {
         return false;
     }           
 }


 function event_socket_request($fp, $cmd) {
    
     if ($fp) {    
         fputs($fp, $cmd."\n\n");    
         usleep(100); //allow time for reponse
         
         $response = "";
         $i = 0;
         $contentlength = 0;
         while (!feof($fp)) {
            $buffer = fgets($fp, 4096);
            if ($contentlength > 0) {
               $response .= $buffer;
            }
            
            if ($contentlength == 0) { //if contentlenght is already don't process again
                if (strlen(trim($buffer)) > 0) { //run only if buffer has content
                    $temparray = split(":", trim($buffer));
                    if ($temparray[0] == "Content-Length") {
                       $contentlength = trim($temparray[1]);
                    }
                }
            }
            
            usleep(100); //allow time for reponse
            
            //optional because of script timeout //don't let while loop become endless
            if ($i > 10000) { break; } 
            
            if ($contentlength > 0) { //is contentlength set
                //stop reading if all content has been read.
                if (strlen($response) >= $contentlength) {  
                   break;
                }
            }
            $i++;
         }
         
         return $response;
     }
     else {
       echo "no handle";
     }
 }

 $fp = event_socket_create($host, $port, $password);
 $cmd = "api help";
  //$cmd = "show channels";
 $response = event_socket_request($fp, $cmd);
 echo $response; 
 fclose($fp); 
?> 
Запускаем файл так:
# php -f /var/www/html/callyshek.php 
На выход должны получить справочную информацию описания api Freeswitch
Valid Commands:
...,,Shutdown,mod_commands
acl,<ip> <list_name>,Compare an ip to an acl list,mod_commands
alias,[add|stickyadd] <alias> <command> | del [<alias>|*],Alias,mod_commands
banner,,Return the system banner,mod_commands
bg_system,<command>,Execute a system command in the background,mod_commands
bgapi,<command>[ <arg>],Execute an api command in a thread,mod_commands
break,<uuid> [all],uuid_break,mod_commands
cdr_csv,parameters,cdr_csv controls,mod_cdr_csv
chat,<proto>|<from>|<to>|<message>|[<content-type>],chat,mod_dptools
coalesce,[^^<delim>]<value1>,<value2>,...,Return first nonempty parameter,mod_commands
complete,add <word>|del [<word>|*],Complete,mod_commands
cond,<expr> ? <true val> : <false val>,Evaluate a conditional,mod_commands
… <………> …
xml_locate,[root | <section> <tag> <tag_attr_name> <tag_attr_val>],Find some xml,mod_commands
xml_wrap,<command> <args>,Wrap another api command in xml,mod_commands

Библиотека Event Socket Library (ESL)

Библиотека Event Socket Library (ESL) предназначена для использования взаимодействия с Freeswitch в различных языках программирования.
Сборка базовой библиотеки Event Socket Library (ESL):
# yum install libxml2-devel pcre-devel bzip2-devel curl-devel gmp-devel aspell-devel libtermcap-devel gdbm-devel db4-devel 
# cd /usr/local/src/freeswitch/libs/esl/
# make
Затем для включения модулей для конкретных языков программирования выполняем:

Для языка php:
# yum install php-devel
# make phpmod-install
ПРИМЕЧАНИЕ:
Если во время установки возникнет следующая ошибка
esl_wrap.cpp:730:18: фатальная ошибка: zend.h: Нет такого файла или каталога
 #include "zend.h"
                  ^
компиляция прервана.
make[1]: *** [esl_wrap.o] Ошибка 1
make[1]: Выход из каталога `/usr/local/src/freeswitch/libs/esl/php'
make: *** [phpmod] Ошибка 2
Необходимо выполнить следующее:
# cd /usr/local/src/freeswitch
# ./configure
Затем повторяем:
# cd /usr/local/src/freeswitch/libs/esl/
# make phpmod-install
Теперь сборка происходит без ошибок.
Теперь после инсталляции ESL для PHP
Конфигурация библиотеки здесь - /etc/php.d/esl.ini
Модуль ESL прописался в /usr/lib64/php/modules и /usr/share/pear
Файл библиотеки здесь  - /usr/share/pear/ESL.php
Его можно использовать в разработке программ ESL.php

Для языка python:
# yum install python-devel
# make pymod-install
Модуль PYTHON прописался в /usr/lib64/python2.7/site-packages/ESL.py

В заключении рестартуем apache:
# systemctl restart httpd.service

Проверяем работу сокетов в режиме inbound с использованием скриптов на PHP и библиотеки ESL.

Для проверки работы библиотеки ESL в языке php создаем файл /var/www/html/callyshek2.php
# touch /var/www/html/callyshek2.php
Его содержимое будет такое:
<?php
require_once('ESL.php');

if ($argc > 1) {
array_shift($argv);
$command = sprintf('%s', implode(' ', $argv));
printf("Command to run is: %s\n", $command);
$sock = new ESLconnection('127.0.0.1', '8021', 'ClueCon');
$res = $sock->api($command);
printf("%s\n", $res->getBody());
} else {
printf("ERROR: You Need To Pass A Command\nUsage:\n\t%s <command>", $argv[0]);
}?>
Запуск для проверки:
# php -f /var/www/html/callyshek2.php status
На выходе должны получить что-то такое:
Command to run is: status
UP 0 years, 0 days, 2 hours, 13 minutes, 46 seconds, 695 milliseconds, 497 microseconds
FreeSWITCH (Version 1.6.9 git d574870 2016-06-13 18:10:44Z 64bit) is ready
0 session(s) since startup
0 session(s) - peak 0, last 5min 0
0 session(s) per Sec out of max 30, peak 0, last 5min 0
1000 session(s) max
min idle cpu 0.00/99.80
Current Stack Size/Max 240K/8192K

Проверка работы Freeswitch в режиме outbound.

В качестве сокет-сервера для проверки будет выступать утилита netcat.

Для того что бы передать управление внешнему серверу и использовать режим outbound прописываем в плане набора:
  <extension name="call_in">
    <condition field="destination_number" expression="^(8459938103)$|^(9938103)$">
        <action application="socket" data="10.200.16.214:8022 async full"/>
    </condition>
  </extension>
Порт 8021, указанный в конфигурации мы использовать не можем, так как этот порт уже занят самим Freeswitch и не может выступать в качестве порта сокет-сервера. Используем здесь порт 8022.
Затем проводим рестарт:
# systemctl restart freeswitch

Запускаем сервер сокет на порту 8022 и ждем:
# nc -k -v -l 10.200.16.214 8022
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Listening on 10.200.16.214:8022
При вызове на номер 8459938103 Freeswitch передаст управление сокет-серверу:
Ncat: Connection from 10.200.16.214.
Ncat: Connection from 10.200.16.214:39806.
Вводим в консоли команду connect и два раза ENTER:
# connect\n\n
Получаем ответ от сервера с данными о поступившем вызове:
Event-Name: CHANNEL_DATA
Core-UUID: 77af8caf-af88-42ab-81ed-b7287794de01
FreeSWITCH-Hostname: callpeg.svttk.ru
FreeSWITCH-Switchname: callpeg.svttk.ru
FreeSWITCH-IPv4: 10.200.16.214
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2016-07-13%2017%3A11%3A07
Event-Date-GMT: Wed,%2013%20Jul%202016%2014%3A11%3A07%20GMT
Event-Date-Timestamp: 1468419067842618
Event-Calling-File: mod_event_socket.c
Event-Calling-Function: parse_command
Event-Calling-Line-Number: 1996
….<….>….
variable_endpoint_disposition: DELAYED%20NEGOTIATION
variable_DP_MATCH: ARRAY%3A%3A8469938103%7C%3A8469938103
variable_call_uuid: ae37202f-c4ab-41dd-b109-8fde03c56223
variable_current_application_data: 10.200.16.214%3A8022%20async%20full
variable_current_application: socket
variable_socket_host: 10.200.16.214
Content-Type: command/reply
Reply-Text: %2BOK%0A
Socket-Mode: async
Control: full
Значит все работает.

Проверка работы Freeswitch в режиме outbound с использованием сокет-сервера на PYTHON.

Создадим файл servcall.py на основе примера, имеющегося в дистрибутиве ESL:
/usr/local/src/freeswitch/libs/esl/python/server.py
Содержимое файла:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import SocketServer
from ESL import *

class ESLRequestHandler(SocketServer.BaseRequestHandler ):
def setup(self):
print self.client_address, 'connected!'

fd = self.request.fileno()
print fd

con = ESLconnection(fd)
print "Connected: ", con.connected()
if con.connected():

info = con.getInfo()

uuid = info.getHeader("unique-id")
print uuid
con.execute("answer", "", uuid)
con.execute("playback", "ttk/fon.wav", uuid);

#server host is a tuple ('host', port)
server = SocketServer.ThreadingTCPServer(('10.200.16.214', 8022), ESLRequestHandler)
server.serve_forever()
В этом файле описывается логика работы сервера. Сервер запускается и прослушивает Ip 10.200.16.214 и порт 8022. При установлении соединения, запрашивается информация о соединении и серверу Freeswitch дается команда проигрывать абоненту звуковой файл ttk/fon.wav.
Файл должен быть помещен в папку: /usr/local/freeswitch/sounds/en/us/callie/ttk/ (папка ttk должна быть создана).
Для работоспособности схемы, необходимо запустить сервер командой:
# ./servcall.py

Вывод событий Freeswitch в stdout linux, используя ESL и PYTHON и подключаясь к Freeswitch в режиме inbound.

В дистрибутиве ESL Freeswitch существует скрипт-сервер, написанный на PYTHON, который подключается к серверу, подписывается на события и выводит все поступившие события в консоль. Скрипт лежит здесь - /usr/local/src/freeswitch/libs/esl/python/events.py
На его основе создадим свой файл-скрипт-сервер events2.py.
Содержимое файла:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import string
import sys

from ESL import *

con = ESLconnection("127.0.0.1","8021","ClueCon")
#are we connected?

if con.connected:

  con.events("plain", "all");

  while 1:
  #my $e = $con->recvEventTimed(100);
    e = con.recvEvent()

    if e:
      print e.serialize()
При запуске этого скрипта все, произойдет подключение к Freeswitch на ip 127.0.0.1 и порт 8021. Устанавливается режим получения сообщений – plain. Параметр all говорит о том, что мы хотим получать все сообщения. Затем все полученные события будут выводиться в консоль.
Пример:
#./events2.py
При поступлении вызова мы увидим множество сообщений:
Event-Name: HEARTBEAT

Event-Name: RE_SCHEDULE

Event-Name: CHANNEL_STATE

Event-Name: CHANNEL_CALLSTATE

Event-Name: PRESENCE_IN

Event-Name: CHANNEL_EXECUTE

Event-Name: API

Event-Name: CHANNEL_OUTGOING

Event-Name: CHANNEL_CREATE

Event-Name: CHANNEL_ORIGINATE

Event-Name: CHANNEL_PROGRESS

Event-Name: CHANNEL_HANGUP

Event-Name: CHANNEL_EXECUTE_COMPLETE

Event-Name: CHANNEL_HANGUP_COMPLETE

Event-Name: CHANNEL_DESTROY


Комментариев нет:

Отправить комментарий