Saturday, November 21, 2015

FreeSwitch G.729

It's exists!
Only A, but normally it's enough.
https://github.com/xadhoom/mod_bcg729

Point: don't forget to modify Makefile with correct FreeSwitch paths.
Usually
FS_INCLUDES=/usr/local/freeswitch/include
FS_MODULES=/usr/local/freeswitch/mod

Little update: can't get it work on 1.6 in VmWare environment. 

Update 2:
Due to forum a bit update for FusionPBX:

apt-get install build-essential automake gcc git libtool pkg-config libfreeswitch-dev

FS_INCLUDES=/usr/include/freeswitch

FS_MODULES=/usr/lib/freeswitch/mod

Tuesday, October 27, 2015

Handling Austrian (and maybe German and Swiss) DID numbers with extensions at A2Billing

In Austria still using classic ISDN dialing rules. So, if your company have number 431234567, and your internal number is 101 you can dial 431234567101 from any phone and get to extension 101 without any crappy IVR's.
Causes several problems with dialing rules, but make life much more comfortable.
So, the problem is how to handle those DID's in A2Billing, cause this soft uses only exact matches.
Solution - use MySQL patterns in DID fields directly.

So, now DID in A2Billing will looks like
 431234567%
 yes with "%" sign at the end.
Also, for VoIP destination was made a quick modification, that allows to use variables in dialstring.
So, destination will be like
SIP/<YourSipUser>/%did%.
At %did% A2billing will automatically place original DID(extension)

Patch is below
diff -Naur AGI/a2billing.php agi-bin/a2billing.php
--- AGI/a2billing.php 2015-02-27 15:42:38.000000000 +0100
+++ agi-bin/a2billing.php 2015-10-27 13:17:49.000000000 +0100
@@ -643,7 +643,8 @@
             " aleg_carrier_initblock_offp, aleg_carrier_increment_offp, aleg_retail_initblock_offp, aleg_retail_increment_offp ".
             " FROM cc_did, cc_did_destination, cc_card ".
             " WHERE id_cc_did=cc_did.id AND cc_card.status=1 AND cc_card.id=id_cc_card AND cc_did_destination.activated=1 ".
-            " AND cc_did.activated=1 AND did='$mydnid' ".
+            //" AND cc_did.activated=1 AND did='$mydnid' ".
+            " AND cc_did.activated=1 AND '$mydnid' LIKE did ". // Added to Austrian DID handling
             " AND cc_did.startingdate<= CURRENT_TIMESTAMP AND (cc_did.expirationdate > CURRENT_TIMESTAMP OR cc_did.expirationdate IS NULL ".
             " AND cc_did_destination.validated=1";
         if ($A2B->config["database"]['dbtype'] != "postgres") {
diff -Naur AGI/lib/Class.A2Billing.php agi-bin/lib/Class.A2Billing.php
--- AGI/lib/Class.A2Billing.php 2015-02-27 15:42:38.000000000 +0100
+++ agi-bin/lib/Class.A2Billing.php 2015-10-27 11:26:36.000000000 +0100
@@ -1324,6 +1324,8 @@
                     $dialparams = str_replace("%timeout%", min($time2call * 1000, $max_long), $dialparams);
                     $dialparams = str_replace("%timeoutsec%", min($time2call, $max_long), $dialparams);
                     $dialstr = $inst_listdestination[4] . $dialparams;
+    // Added support for %did% variable
+    $dialstr = str_replace("%did%", $this->orig_ext, $dialstr);

                     $this->debug(DEBUG, $agi, __FILE__, __LINE__, "[A2Billing] DID call friend: Dialing '$dialstr' Friend.\n");


Saturday, July 4, 2015

A little update....

From now, all articles in this blog are to be written in English (or pretending to be :) ).
This is done, cause English is a modern Latin. You may not like it, but it is.
With this change I want to share my notes not only with Russian-speaking audience.
And to be honest, most info in my blog was taken from english-written resources.

Wednesday, May 27, 2015

Veeam Backup и PowerShell. Бэкапы по расписанию.

Чудный продукт Veeam Backup and Replication, начиная с версии 8 (Patch 2) открыл небольшую лазейку для совершения бэкапов по расписанию, точнее, VeeamZIP.
Точнее - в бесплатной версии заработал cmdlet  Start-VBRZip.
Итак, чем это грозит и как это можно использовать. А можно просто напилить скрипт, который будет делать полные бэкапы виртуальных машин. Ну и запихнуть его в планировщик.

Естественно, надо установить Veeam PowerShellSDK. Ну и в самом Veeam настроить все сервера.

Собственно, сам скрипт:
 param (
    [string]$csvFile = 'C:\ToBackup.txt', 
    [int]$storeDays = 7
    )

$doVMBackup = {
    param ($VMHost, $VMName, $VMStorePath, $VMLimitStore)
    try {
        Add-PSSnapin VeeamPSSnapin
        $viServerObject = Get-VBRServer -Name $VMHost
        $viObject = Find-VBRViEntity -Server $viServerObject -Name $VMName
        Start-VBRZip -Folder $VMStorePath -Entity $viObject
        if ($VMLimitStore -ne -1) {

            $limit = (Get-Date).AddDays(-$VMLimitStore)
            # Delete files older than the $limit.
            Get-ChildItem -Path $VMStorePath -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force

            # Delete any empty directories left behind after deleting the old files.
            Get-ChildItem -Path $VMStorePath -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse
        }
    }
    catch {
        write-host "An error occured...."
    }
}

$VMTotal = Import-Csv $csvFile
foreach ($VM in $VMTotal) {
    Start-Job -ScriptBlock $doVMBackup -ArgumentList $VM.Host,$VM.Name,$VM.Path,$storeDays



Скрипт, как параметр принимает 2 параметра - CSV файл c машинами, которые надо бэкапить и срок хранения бэкапов. Естественно, этот механизм можно переделать под себя.
Благодаря использованию Job задачи идут параллельно :)

Формат CSV файла:
Host, Name, Path
"10.10.10.10","MyVM","C:\BKPTest"
"<Host name in Veeam>","<VM name>","<Path to Backup>"

Спасибо ресурсам
http://memos.nomblot.org/veeam
http://go.veeam.com/learn-powershell-basics-free-tutorial-course/
Хабру и StackOverflow :)

Wednesday, May 6, 2015

Сервис определения страны по IP

Итак, возникла необходимость вычислять, так сказать по IP. А точнее - определять страну для автоматической подстановки телефонного кода. Как оказалось, за эту услугу хотят брать денюжки, например тут. Но хочется то халявного. Ну и по общим правилам опенсорса, если что-то надо - напиши сам. Тем более, база, пусть и не такая точная есть у того же MaxMind
Есть также и такая штука, но я что-то не очень уверен в ее скорости. Но можно попробовать.
В любом случае, родился такой сервис.
Абсолютно бесплатно, поддерживает https.
Используется связка Nginx + uwsgi + flask + postgresql. Почему так? Потому что хотелось попробовать, что есть фласк и постгре. Написано крайне коряво, потому что только учусь. Сейчас получается каждый запрос - это отдельное подключение к базе. Т.к. это все-таки сервер есть мнение переписать это под вариант одно подключение - много запросов и подключение держать постоянно. Но как это сделать я пока не знаю, посмотрим на нагрузку.
Ну и по традиции - код. Да, я знаю, он ужасен, в связи с чем попрошу в комментах наставить на путь истинный.

--
upd:

  • Постарался сделать так, чтобы соединение к базе было одно, а не дергать каждый раз.
  • Добавил метод автоопределения места запроса https://geoip.webcall.today/getcountry?ip=auto



from flask import Flask,request,jsonify
import socket
import psycopg2

def ip_address_is_valid(address):
    try: socket.inet_aton(address)
    except socket.error: return False
    else: return address.count('.') == 3

application = Flask(__name__)
con  = psycopg2.connect(database='geoip', user='geoip', host='localhost', password = 'geoipuser')

@application.route('/')
def hello_world():
    return "<b>Geoserver welcomes you. Usage = https://geoip.webcall.today/getcountry?ip=X.X.X.X</b><br> This product includes GeoLite2 data created by MaxMind, available from<br> <a href=\"http://www.maxmind.com\">http://www.maxmind.com</a>."

@application.route('/getcountry')
def getcountry():
    global con
    ip = request.args.get('ip', '')
    if ip == 'auto':
        ip = '%s' % request.remote_addr
    if ip_address_is_valid(ip):
        try:
            cur = con.cursor()
            cur.execute("SELECT code,location,fullname FROM countrydata WHERE geodata = (SELECT geodata FROM ipdata WHERE subnet >>= '%s'::inet LIMIT 1) LIMIT 1" % ip)
            result = cur.fetchone()
            if result:
                result = jsonify(code = result[0],
                                   location = result[1],
                                   name = result[2]), 200
            else:
                result = jsonify(code = 'none',
                                 location = 'None',
                                 name = 'None'), 404
        except psycopg2.DatabaseError, e:
            if con:
                con.rollback()
            con  = psycopg2.connect(database='geoip', user='geoip', host='localhost', password = 'geoipuser')
            result = jsonify(code = 'Error %s' % e,
                            location = 'None',
                            name = 'None'), 500
        finally:
            pass
    else:
        result = jsonify(code = 'none',
                         location = 'None',
                         name = 'None'), 404
    return result

if __name__ == '__main__':
    application.run(host='127.0.0.1')


Ну и ip_address_is_valid можно переписать с использованием регулярок. Что-то типа

--import socket
++import re

def ip_address_is_valid(address):
    regex_ip = "^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$"
    if re.match(regex_ip, address):
        return True
    return False