Быстрое развертывание телефонной сети на Asterisk+Cisco

    Так случилось, что в короткие сроки появилась необходимость перевезти 70 человек с аналоговыми телефонами из одного бизнес центра в другой. Ситуация усугублялась тем, что в новом офисе у владельца не было аналоговых портов в АТС, а АТС в старом принадлежала телефонной компании. Пришлось в короткие сроки внедрять IP телефонию с переносом всех аналоговых городских линий на Asterisk. Поставка оборудования была назначена на день предшествующий дню переезда, что означало — времени на развертывание телефонии будет очень мало.

    Что из этого вышло под катом.
    Материала много, так что не пугайтесь.

    Итак имеющаяся конфигурация:
    • 60 пользователей со своими компьютерами.
    • 60 телефонов Cisco SPA 502G
    • 4 панели Cisco SPA500S
    • 5 баз Siemens C610A с парой трубок к каждой.
    • Один сервер на FreeBSD.
    • Пара свичей Cisco SF300-24P


    Поискав и почитав в сети множество документов связанных с autoprovision мы решили воспользоваться возможностью asterisk выполнять автонастройку устройств самостоятельно. Всё бы ничего, но как оказалось автонастройка через sql базу в asterisk не позволяет мониторить состояние экстеншена на доппанелях. Т.е. нельзя настроить hint(по крайней мере в августе 2012 оно не поддерживалось). Имея в наличии пару телефонов мы решились на написание своего autoprovision.

    Начали с настройки сервера и свичей. Создали 2 VLAN с номерами 204 и 214. Первый для локальной сети, второй для IP телефонии. Т.к. телефоны имеют встроенный конфигурируемый коммутатор, это было лучшим решением с нашей точки зрения.
    Выставили порты на коммутаторах к которым подключаются пользователи в транк и изменили native vlan.
    interface fastethernet1
    switchport trunk allowed vlan add 204
    switchport trunk native vlan 214
    exit
    
    Подключили сервер к коммутатору в транковый порт и поменяли настройки dhcpd.conf
    # Phones Subnet Vlan 214
    subnet 172.16.214.0 netmask 255.255.255.0 {
            range 172.16.214.10 172.16.214.250;
            option routers 172.16.214.1;
            option tftp-server-name "http://172.16.214.1/XMLDefault.cnf.xml";
            option domain-name "phones.mydomain.local";
            option domain-name-servers 172.16.214.1;
            option broadcast-address 172.16.214.255;
            ddns-updates on;
            ddns-domainname "phones.mydomain.local";
            ddns-rev-domainname "in-addr.arpa";
    }
    
    # Computers Subnet Vlan 204
    subnet 172.16.6.0 netmask 255.255.255.0 {
      range 172.16.6.12 172.16.6.240;
      option broadcast-address 172.16.6.255;
      option domain-name-servers 172.16.6.1;
      option domain-name "mydomain.local";
      option routers 172.16.6.1;
      if option host-name = "" {
            option host-name = concat ("dev-", binary-to-ascii( 10, 8, "", substring( reverse( 1, leased-address), 0, 1)));
            ddns-hostname = concat ("dev-", binary-to-ascii( 10, 8, "", substring( reverse( 1, leased-address), 0, 1)));
      }
    }
    

    Отлично. Адресочки раздаются, телефоны бутятся, пришло время запилить базовый конфиг.
    Поднимаем thttpd и кладём ему в корень XMLDefault.cnf.xml
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <flat-profile xmlns="http://www.sipura.net/xsd/SPA50x-30x-SIP" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sipura.net/xsd/SPA50x-30x-SIP http://www.sipura.net/xsd/SPA50x-30x-SIP/SPA50x-30x-SIP-7-5-2.xsd">
    
    <Admin_Passwd ua="na">7654321</Admin_Passwd>
    <SPCP_Auto-detect ua="na">No</SPCP_Auto-detect>
    <Domain ua="rw">phones.mydomain.local</Domain>
    <Primary_DNS ua="rw">172.16.214.1</Primary_DNS>
    <Syslog_Server ua="na">172.16.214.1</Syslog_Server>
    <Debug_Server ua="na">172.16.214.1</Debug_Server>
    <Debug_Level ua="na">0</Debug_Level>
    <Primary_NTP_Server ua="na">172.16.214.1</Primary_NTP_Server>
    <Enable_VLAN ua="rw">Yes</Enable_VLAN>
    <Enable_PC_Port_VLAN_Tagging ua="na">Yes</Enable_PC_Port_VLAN_Tagging>
    <Enable_CDP ua="na">No</Enable_CDP>
    <Enable_LLDP-MED ua="na">No</Enable_LLDP-MED>
    <PC_Port_VLAN_ID ua="na">204</PC_Port_VLAN_ID>
    <Profile_Rule ua="na">http://172.16.214.1/XMLDefault.cnf.xml</Profile_Rule>
    <Profile_Rule_B ua="na">http://172.16.214.1/cfg/cfg.cgi?SN=$SN&MAC=$MA</Profile_Rule_B>
    <Key_System_Auto_Discovery ua="na">No</Key_System_Auto_Discovery>
    <G722_Enable_1_ ua="na">Yes</G722_Enable_1_>
    <L16_Enable_1_ ua="na">No</L16_Enable_1_>
    <G726-16_Enable_1_ ua="na">No</G726-16_Enable_1_>
    <G726-24_Enable_1_ ua="na">No</G726-24_Enable_1_>
    <G726-32_Enable_1_ ua="na">No</G726-32_Enable_1_>
    <G726-40_Enable_1_ ua="na">No</G726-40_Enable_1_>
    <Enable_IP_Dialing_1_ ua="na">No</Enable_IP_Dialing_1_>
    <Use_Remote_Pref_Codec_1_ ua="na">Yes</Use_Remote_Pref_Codec_1_>
    <Time_Format ua="rw">24hr</Time_Format>
    <Date_Format ua="rw">day/month</Date_Format>
    <Text_Logo ua="na">Company</Text_Logo>
    <Time_Zone ua="na">GMT+04:00</Time_Zone>
    <Upgrade_Rule ua="na">( $SWVER ne 7.5.2b )? http://172.16.214.1/sw/spa50x-30x-7-5-2b.bin</Upgrade_Rule>
    </flat-profile>
    

    Таким образом, после того как телефон возьмёт из dhcp параметры автоконфигурации и заберет с сервера базовый конфиг, он сделает вторую итерацию за конфигурационным файлом привязанному к серийному номеру устройства и его мак адресу. После полной инициализации конфига, телефон переведёт порт компьютера в 204 VLAN, обеспечив ему работу с локальной сетью.
    Перейдём к тому как телефоны получают свой конфигурационный файл.
    Создадим вспомогательный файл с настройками /usr/local/etc/astprov.conf
    sqlite="/usr/local/bin/sqlite3"
    ast_provisiondb="/var/db/asterisk/asterisk_provision.sqlite3"
    ast_ext_dialplan="/var/db/asterisk/asterisk_ext_dialplan.conf"
    ast_ext_accounts="/var/db/asterisk/asterisk_ext_accounts.conf"
    logger_tag="astprov"
    include="/etc/rc.conf"
    


    Создадим по указанному в конфигурационном файле базу sqlite3 следующей структуры:
    PRAGMA foreign_keys=OFF;
    BEGIN TRANSACTION;
    CREATE TABLE `provision` (
    `macaddress` varchar(12) NOT NULL,
    `serial` varchar(12) NOT NULL,
    `secret` varchar(32) NOT NULL,
    `ext` int(11) NOT NULL,
    `fullname` varchar(64) NOT NULL,
    `callerid` varchar(64) NOT NULL,
    `callgroup` varchar(32) NOT NULL default '1',
    `pickupgroup` varchar(32) NOT NULL default '1',
    `context` varchar(32) NOT NULL,
    `subscribecontext` varchar(32) NOT NULL default '1',
    `ip` varchar(15) NOT NULL);
    COMMIT;
    


    Для последующей быстрой настройки рабочих мест создаём файл с телефонами
    3027|Buhgalter
    3097|Igor
    3018|Sergey
    3016|Oleg
    3091|Vladimir
    3014|Ekaterina
    3012|Andrey
    3015|Maxim
    


    и скармливаем его скрипту в качестве параметра
    #!/bin/sh
    set -x
    . /usr/local/etc/astprov.subr
    
    context='local_pool'
    callgroup=1
    pickupgroup=1
    subscribecontext=1
    ip="none"
    cat $1 | while IFS= read -r line; do
            randomstr=`< /dev/urandom tr -dc A-Za-z0-9 | head -c10`
            randompas=`< /dev/urandom tr -dc A-Za-z0-9 | head -c10`
            ext=$(echo $line | cut -d '|' -f1 )
            fullname=$(echo $line | cut -d '|' -f2 )
            # fullname <<< $(IFS=";"; echo $line)
            insertline="insert into provision values ('$randomstr','$randompas','$randompas',$ext,'$fullname','$fullname <$ext>',$callgroup,$pickupgroup,'$context',$subscribecontext,'$ip')"
    #       echo $insertline
            $sqlitecmd "$insertline"
    done
    

    скрипт автоматически заполнит базу и даст возможность быстрого подключения новых телефонов.

    Теперь немного колдовства.
    Asterisk работает под учёткой asterisk:asterisk, thttpd работает под www:www. Поэтому создаём группу astprov куда добавляем asterisk и www.
    # chown -R asterisk.astprov /var/db/asterisk
    # chmod 0775 /var/db/asterisk
    # chmod 0664 /var/db/asterisk/*
    


    Теперь нужно персонализировать настройки телефонов.
    Создаём скрипт /usr/local/www/data/cfg/cfg.cgi, я люблю perl посему конфигуратор на перле.
    #!/usr/bin/perl
    
    use strict;
    #use Data::Dumper;
    use DBI;
    use vars qw/%sv %form %cookie %rq $sth $dbh $config/;
    use FileHandle;
    use Sys::Syslog qw(:standard);
    
    my $configfilename="/usr/local/etc/astprov.conf";
    $config=_read_config_file($configfilename);
    
    &systeminit;
    &printhead;
    #$form{SN}='CBT1602095Z';
    #$form{MAC}='649ef37761c2';
    #$sv{ip}="172.16.214.10";
    
    openlog("astprov", 'cons,pid');
    
    exit(1) if (not defined $form{SN} or not defined $form{MAC});
    
    &baseconnect;
    &get_info;
    &baseclose;
    closelog();
    exit(1);
    
    sub get_info
    {
    $form{SN} =~ s/[^0-9A-Za-z]//g;
    $form{MAC} =~ s/[^0-9A-Za-z]//g;
    my $dbdata;
    if($dbdata = &request_phone_info)
        {
            &print_xml($dbdata);
        }
    else
    {
        if(keys %{$dbdata} < 1)
        {
            &insert_new_phone;
        }
        $dbdata = &request_phone_info;
        &print_xml($dbdata);
    }
    #print Dumper $dbdata;
    }
    
    sub print_xml
    {
    my $dbdata=shift;
    my $additional;
    if(-f "/usr/local/www/data/cfg/additional".lc($form{MAC}).".xml")
    {
        open IN,"</usr/local/www/data/cfg/additional".lc($form{MAC}).".xml";
        undef $/;
        $additional = <IN>;
        close IN;
    }
    my $hostname = "office-".$dbdata->{ext};
    print << "[end]";
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <flat-profile xmlns="http://www.sipura.net/xsd/SPA50x-30x-SIP" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sipura.net/xsd/SPA50x-30x-SIP http://www.sipura.net/xsd/SPA50x-30x-SIP/SPA50x-30x-SIP-7-5-2.xsd">
            <HostName ua="rw">$hostname</HostName>
            <Phone-UI-readonly ua="na">Yes</Phone-UI-readonly>
            <Phone-UI-user-mode ua="na">Yes</Phone-UI-user-mode>
            <Proxy_1_ ua="na">172.16.214.1</Proxy_1_>
            <Display_Name_1_ ua="na">$dbdata->{fullname}</Display_Name_1_>
            <User_ID_1_ ua="na">$dbdata->{ext}</User_ID_1_>
            <Password_1_ ua="na">$dbdata->{secret}</Password_1_>
            <Dial_Plan_1_ ua="na">(7[0-9][0-9]xxxxS0|*8|*xx|xxxx|0xxxxxxxxxxxx.)</Dial_Plan_1_>
            <Station_Name ua="na">$dbdata->{ext}</Station_Name>
            <Station_Display_Name ua="na">$dbdata->{fullname}</Station_Display_Name>
            <Server_Type ua="na">Asterisk</Server_Type>
            <XML_Directory_Service_Name ua="na">My Company</XML_Directory_Service_Name>
            <XML_Directory_Service_URL ua="na">http://172.16.213.1/directory.cgi</XML_Directory_Service_URL>
    $additional
    </flat-profile>
    [end]
    }
    
    sub request_phone_info
    {
    my $cmd = "select ext,callerid,fullname,secret from provision where macaddress='".lc($form{MAC})."' and serial='".lc($form{SN})."'";
    my $dbdata = $dbh->selectrow_hashref($cmd);
    if($dbh->err)
    {
        print "cmd = ",$cmd,"\n";
        print "err = ",$dbh->err,"\n";
        print "errstr = ",$dbh->errstr,"\n";
        print "state = ",$dbh->state,"\n";
        return undef;
    }
    return $dbdata;
    }
    
    sub insert_new_phone
    {
        my $maxnum = $dbh->selectrow_array("SELECT MAX(ext) FROM provision where ext < 2000") || "1000";
        $maxnum++;
        $dbh->do("INSERT INTO provision (ip,macaddress,serial,secret,ext,fullname,callerid,context) VALUES ('".join ("','",$sv{ip},lc($form{MAC}),lc($form{SN}),lc($form{SN}),$maxnum,'Unregistered','Unregistered <'.$maxnum.'>','unreg')."')");
        syslog('info|local7',"New host added MAC:$form{MAC} SN:$form{SN}");
    
        if($dbh->err)
        {
            print "err = ",$dbh->err,"\n";
            print "errstr = ",$dbh->errstr,"\n";
            print "state = ",$dbh->state,"\n";
            exit(1);
        }
    }
    
    
    sub systeminit
    {
    $sv{"ip"} = $ENV{"REMOTE_ADDR"};
    $sv{"userhost"} = $ENV{"REMOTE_HOST"};
    $sv{"url"} = $ENV{"HTTP_HOST"};
    $sv{"doc"} = $ENV{"DOCUMENT_ROOT"};
    $sv{"ref"} = $ENV{"HTTP_REFERER"};
    
    # ----------------------------------------------------------------------------------------
    
    my $request_url = $ENV{"REQUEST_URI"};
    $request_url =~ s/%(..)/pack("c",hex($1))/ge;
    $request_url =~ s/[^A-Za-z0-9\-\_\+\=\:\.\,\/\@]//g;
    $request_url =~ s/([\-\_\+\=\.\:\,\/\@\s]){2,}/$1/g;
    $request_url =~ s/\+/\&/g; $request_url =~ s/^\/+//g;
    my $count = 0;
    while ($request_url =~ /^([\w\-\=\_\&\.\,\:\@\s]+)\//)
    {       $rq{$count} = $1; $request_url =~ s/$rq{$count}\///; $count++;  }
    $request_url =~ s/^\s+$//g;
    chomp $request_url;
    $rq{$count} = $request_url if (length($request_url) > 0);
    
    # ----------------------------------------------------------------------------------------
    
    if ( defined($ENV{"HTTP_COOKIE"}) && length($ENV{"HTTP_COOKIE"}) > 0 )
    {
        my @cookies = split(/;/,$ENV{"HTTP_COOKIE"});
        foreach (@cookies)
        {
            my ($name,$value) = split(/=/,$_);
            $cookie{$name} = $value;
        }
    }
    
    # ----------------------------------------------------------------------------------------
    
        if ((defined($ENV{"QUERY_STRING"}) && length($ENV{"QUERY_STRING"}) != 0) || (defined($ENV{"CONTENT_LENGTH"}) && $ENV{"CONTENT_LENGTH"} != 0))
        {
            my $data = undef;
            my @data = undef;
                if ($ENV{"REQUEST_METHOD"} eq "GET")
                {
                    $data = $ENV{"QUERY_STRING"};
                }
                else
                {
                    read(STDIN,$data,$ENV{"CONTENT_LENGTH"});
                }
            @data = split(/&/,$data);
                foreach (@data)
                {
                    $_ =~ s/\+/ /g;
                    my ($name, $value) = split(/=/,$_,2);
                    $name =~ s/%(..)/pack("c",hex($1))/ge;
                    $name =~ tr/[^A-Za-z0-9\-\_\$\+\=\~\.\,]//;
                    $value =~ s/%(..)/pack("c",hex($1))/ge;
                    $form{$name} .= "\0" if (defined($form{$name}));
                    $form{$name} .= $value;
                }
        }
    }
    
    sub baseconnect
    {
    my $dbname = eval $config->{ast_provisiondb};
    $dbh = DBI->connect("dbi:SQLite:dbname=$dbname","","");
    $sth = $dbh->table_info('%', '%', 'provision');
    my $result = $sth->fetchall_hashref('TABLE_NAME');
    if(!defined($result->{'provision'}))
    {
    $dbh->do("
    CREATE TABLE `provision` (
    `macaddress` varchar(12) NOT NULL,
    `serial` varchar(12) NOT NULL,
    `secret` varchar(32) NOT NULL,
    `ext` int(11) NOT NULL,
    `fullname` varchar(64) NOT NULL,
    `callerid` varchar(64) NOT NULL,
    `callgroup` varchar(32) NOT NULL default '1',
    `pickupgroup` varchar(32) NOT NULL default '1',
    `context` varchar(32) NOT NULL,
    `subscribecontext` varchar(32) NOT NULL default 'internal_phones',
    `ip` varchar(15) NOT NULL)
    ");
    
    }
    }
    
    sub baseclose
    {
    if ($dbh->{Active} && defined($sth)) { $sth->finish };
    $dbh->disconnect;
    }
    
    sub printhead
    {
    print "Content-Type: text/plain; charset=UTF-8;\r\n\r\n";
    }
    
    
    sub _read_config_file {
        my $file = shift or return;
    
        my $conf = {};
        my $FH = new FileHandle;
        $FH->open("$file") or (
                            warn(loc(q[Could not open config file '%1': %2],$file,$!)),
                            return {}
                        );
    
        while(<$FH>) {
            next if     /\s*#/;
            next unless /\S/;
    
            chomp; s/^\s*//; s/\s*$//;
    
            my ($param,$val) = split /\s*=\s*/;
    
            ### add these to the config hash ###
            $conf->{ lc $param } = $val;
        }
        close $FH;
    
        return $conf;
    }
    
    


    Скрипт получает из запроса MAC устройства и его серийный номер, если связка есть в базе, то телефону отдаётся конфиг с логином/паролем и параметрами подключения к серверу. Если связки нет, то телефон добавляется в базу, ему присваивается «сервисный номер», в данном случае от 1000 до 2000 и телефону отдаётся конфиг. Если в папку "cfg" положить файл с именем additional<MAC>.xml, то телефону в конфиг добавится данный файл. Мы используем данную возможность для загрузки в телефон настроек панелей. Например:
    <Attendant_Console_Call_Pickup_Code ua="na">*8</Attendant_Console_Call_Pickup_Code>
    <Unit_1_Key_1 ua="na">fnc=sd+cp+blf;sub=2010@172.16.213.1;nme=2010</Unit_1_Key_1>
    <Unit_1_Key_2 ua="na">fnc=sd+cp+blf;sub=2012@172.16.213.1;nme=2012</Unit_1_Key_2>
    <Unit_1_Key_3 ua="na">fnc=sd+cp+blf;sub=2014@172.16.213.1;nme=2014</Unit_1_Key_3>
    <Unit_1_Key_4 ua="na">fnc=sd+cp+blf;sub=2100@172.16.213.1;nme=2100</Unit_1_Key_4>
    <Unit_1_Key_5 ua="na">fnc=sd+cp+blf;sub=2110@172.16.213.1;nme=2110</Unit_1_Key_5>
    <Unit_1_Key_6 ua="na">fnc=sd+cp+blf;sub=2111@172.16.213.1;nme=2111</Unit_1_Key_6>
    <Unit_1_Key_7 ua="na">fnc=sd+cp+blf;sub=2112@172.16.213.1;nme=2112</Unit_1_Key_7>
    <Unit_1_Key_8 ua="na">fnc=sd+cp+blf;sub=2120@172.16.213.1;nme=2120</Unit_1_Key_8>
    <Unit_1_Key_9 ua="na">fnc=sd+cp+blf;sub=2121@172.16.213.1;nme=2121</Unit_1_Key_9>
    

    или чтобы поменять настройки порта для перевода его в другой VLAN
    <Enable_VLAN ua="rw">Yes</Enable_VLAN>
    <Enable_PC_Port_VLAN_Tagging ua="na">Yes</Enable_PC_Port_VLAN_Tagging>
    <Enable_CDP ua="na">No</Enable_CDP>
    <Enable_LLDP-MED ua="na">No</Enable_LLDP-MED>
    <PC_Port_VLAN_ID ua="na">300</PC_Port_VLAN_ID>
    


    Проверяем корректно ли грузятся телефоны и корректно ли им отдаётся конфиг.
    Можно сделать это руками, заглянув в thttpd.log и скопировав запрос в браузер.

    Если у нас всё хорошо, то приступаем к наиболее интересной части, а именно автоматике и настройкам астериска. Каждый пишет на том, на чём ему удобно, но уж коли у нас есть возможность порулить sqlite3 через консоль я решил написать железобетонные скрипты на shell. С первого взгляда страшно, но в итоге получилось очень компактно. Основную часть занимают обработки ошибок для интерактива с asterisk. Все действия производимые в режиме интерактивного управления из asterisk логируются в системный журнал. Почти во всех скриптах включен режим останова при возникновении внутренней ошибки. Если ниже по тексту в имени скрипта отсутствует путь, значит он лежит в /usr/local/etc/asterisk/scripts/.

    В конфиг extensions.conf добавляем
    #include "/var/db/asterisk/asterisk_ext_dialplan.conf"

    В конфиг sip.conf добавляем
    #include "/var/db/asterisk/asterisk_ext_accounts.conf"

    Для того чтобы выполнять команды напрямую из asterisk создадим группу скриптов:
    /usr/local/etc/astprov.subr — основной скрипт содержащий все процедуры работы с базой данных.
    #!/bin/sh
    
    . /usr/local/etc/astprov.conf
    
    #set -x
    
    getvalue()
    {
            result=
            _request=$1
            _ext=$2
            selectcmd='select '${_request}' from provision where ext='${_ext}';'
            result=`$sqlitecmd "$selectcmd"`
            if [ "$result" ]; then
                    echo $result
                    return 0
            else
                    syslog "Error: SQL request cannot be made (request ${_request},extention ${_ext})"
                    return 1
            fi
    }
    
    checkvalue()
    {
            result=
            _request=$1
            _ext=$2
            selectcmd='select '${_request}' from provision where ext='${_ext}';'
            result=`$sqlitecmd "$selectcmd"`
            echo $result
            return 0
    }
    
    setvalue()
    {
            result=
            _what=$1
            _new=$2
            _where=$3
            _old=$4
            updatecmd="update provision set ${_what}='${_new}' where ${_where}='${_old}';"
            $sqlitecmd "$updatecmd"
            if [ $? -ne 0 ]; then
                    syslog "Error: SQL update cannot be made (set ${_what}=${_new} where ${_where}=${_old})"
                    return 1
            else
                    return 0
            fi
    }
    
    get_script_name()
    {
             result=
             result=`echo $0 | rev | cut -d/ -f1 | rev`
             echo $result
    }
    
    syslog()
    {
            result=
            _message=$@
            logger -t $logger_tag `get_script_name`": ${_message}"
    }
    
    sqlitecmdcoln="$sqlite -column $ast_provisiondb"
    sqlitecmdline="$sqlite -line $ast_provisiondb"
    sqlitecmd="$sqlite $ast_provisiondb"
    


    showall.sh дамп базы для диагностики
    #!/bin/sh
    set -e
    . /usr/local/etc/astprov.subr
    $sqlitecmd 'select * from provision'
    

    rebuildlist.sh скрипт обновления динамической конфигурации для asterisk.
    #!/bin/sh
    
    set -e
    
    . /usr/local/etc/astprov.subr
    
    if [ -f $ast_ext_accounts -a ! -w $ast_ext_accounts ]; then
            syslog 'Cannot write to astprov extension database'
            exit 1
    fi
    if [ -f $ast_ext_accounts ]; then
            mv $ast_ext_accounts $ast_ext_accounts.backup
    fi
    if [ -f $ast_ext_dialplan -a ! -w $ast_ext_dialplan ]; then
            syslog 'Cannot write to astprov extension dialplan'
            exit 1
    fi
    if [ -f $ast_ext_dialplan ]; then
            mv $ast_ext_dialplan $ast_ext_dialplan.backup
    fi
    echo '[dynamic_internal_numbers]' >> $ast_ext_dialplan
    selectcmd='select ext from provision order by ext;'
    for ext in `$sqlitecmdcoln "$selectcmd"`; do
            echo 'exten => '$ext',1,dumpchan()' >> $ast_ext_dialplan
            echo 'same => n,Dial(SIP/'$ext',60,Tt)' >> $ast_ext_dialplan
            echo 'same => hint,SIP/'$ext >> $ast_ext_dialplan
            echo 'same => n,Hangup()' >> $ast_ext_dialplan
            echo ''>> $ast_ext_dialplan
            selectcmd='select callerid,fullname,macaddress,secret,context,callgroup,pickupgroup,subscribecontext,ip from provision where ext='$ext';'
            echo '['$ext'](all)' >> $ast_ext_accounts
    $sqlitecmdline "$selectcmd" | sed -E 's/ = /=/g' |
                    while IFS= read -r line; do
                    echo $line >> $ast_ext_accounts
            done
            echo >> $ast_ext_accounts
    done
    syslog 'Asterisk dynamic list succesfully updated'
    

    changenumber.sh скрипт замены номера телефона на другой.
    #!/bin/sh
    
    #set -x
    
    . /usr/local/etc/astprov.subr
    
    oldname=`getvalue 'ext' $1`
    if [ "$?" -ne "0" ]; then
            echo -n GETERR
            exit
    fi
    numberexist=`checkvalue 'ext' $2`
    if [ -z "$numberexist" ]; then
            setvalue 'ext' $2 'ext' $1
            if [ "$?" -ne "0" ]; then
                    echo -n SETERR
                    exit
            fi
            setvalue 'callerid' "$oldname <$2>" 'ext' $2
            if [ "$?" -ne "0" ]; then
                    echo -n SETERR
                    exit
            fi
            syslog "Number $1 renamed to $2"
            echo -n OK
    else
            syslog "Number $1 cannot be renamed to $2. Number $2 already exist"
            echo -n NUMEXIST
            exit
    #       return 1
    fi
    

    togglenumbers.sh скрипт для смены местами телефонов. скажем нужно ext 1023 поменять местами с 2035. Очень удобно когда пересаживаются сотрудники. Телефоны остаются на месте, а номера меняются местами.
    #!/bin/sh
    
    #set -x
    
    . /usr/local/etc/astprov.subr
    
    macaddress_from=`getvalue 'macaddress' $1`
    if [ "$?" -ne "0" ]; then echo -n GETERR ; exit ; fi
    
    macaddress_to=`getvalue 'macaddress' $2`
    if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi
    
    secret_from=`getvalue 'secret' $1`
    if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi
    
    secret_to=`getvalue 'secret' $2`
    if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi
    
    serial_from=`getvalue 'serial' $1`
    if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi
    
    serial_to=`getvalue 'serial' $2`
    if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi
    
    setvalue macaddress $macaddress_from'_'$macaddress_to macaddress $macaddress_from
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    setvalue macaddress $macaddress_to'_'$macaddress_from macaddress $macaddress_to
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    setvalue secret $secret_to macaddress $macaddress_from'_'$macaddress_to
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    setvalue secret $secret_from macaddress $macaddress_to'_'$macaddress_from
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    setvalue serial $serial_to macaddress $macaddress_from'_'$macaddress_to
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    setvalue serial $serial_from macaddress $macaddress_to'_'$macaddress_from
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    setvalue macaddress $macaddress_to macaddress $macaddress_from'_'$macaddress_to
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    setvalue macaddress $macaddress_from macaddress $macaddress_to'_'$macaddress_from
    if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi
    
    syslog "Phones $1($serial_from:$macaddress_from) and $2($serial_to:$macaddress_to) are reversed"
    echo -n OK
    


    Скрипты выложены. В принципе можно использовать скрипты и руками через
    su -m asterisk, но лучший вариант интегрировать данную «фичу» в asterisk.
    Т.к. все новые незарегистрированные телефоны попадают в гостевой контекст "[unreg]", то нужно создать в asterisk описание этого контекста.

    ;goto service menu
    exten => 3999,1,Goto(unreg,_X.,1)
    
    [unreg]
    exten => _X.,1,Answer()
    ;same => n,Authenticate(040478)
    same => n,NoOp(Entering Service Menu)
    same => n,Playback(ivr/prov-welcome-to-service-menu)
    same => n(menu),Background(ivr/change-number&ivr/swap-numbers&ivr/rebuild-list&ivr/reboot-phone&ivr/reload-server)
    same => n,Read(CHOICE,,1,,1,10)
    ; 1 - Change Number
    ; 2 - Swap Numbers
    ; 3 - Rebuild extensions list
    ; 4 - Reload phone
    ; 5 - Reload server
    
    ; Rename Number
    exten => 1,1,Read(RENAMETO,ivr/enter-dest-number,4,,1,5)
    same => n,GotoIf($["${RENAMETO}" = ""]?menu)
    same => n,Playback(ivr/entered-number)
    same => n,SayDigits(${RENAMETO})
    same => n,Read(APPROVE,ivr/press-1-for-accept,1,,1,5)
    same => n,GotoIf($["${APPROVE}" != "1"]?menu)
    same => n,Set(CHGNM=${SHELL(/usr/local/etc/asterisk/scripts/changenumber.sh ${CALLERID(num)} ${RENAMETO})})
    same => n,NoOp(${CHGNM})
    same => n,GotoIf($["${CHGNM}" = "GETERR"]?geterr)
    same => n,GotoIf($["${CHGNM}" = "SETERR"]?seterr)
    same => n,GotoIf($["${CHGNM}" = "NUMEXIST"]?numexist)
    same => n,GotoIf($["${CHGNM}" = "OK"]?ok)
    same => n(ok),Playback(ivr/prov-saved&privacy-thankyou)
    same => n,System(/usr/local/etc/asterisk/scripts/rebuildlist.sh)
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/rebuild-ok)
    same => n,System(/usr/local/sbin/asterisk -rx "sip notify cisco-check-cfg ${CALLERID(num)}")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/send-phone-reboot-ok)
    same => n,System(/usr/local/sbin/asterisk -rx "reload")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/server-reload-ok)
    same => n,Hangup()
    same => n(geterr),Playback(ivr/script-get-error)
    same => n,Hangup()
    same => n(seterr),Playback(ivr/script-set-error)
    same => n,Hangup()
    same => n(numexist),Playback(ivr/prov-exist)
    same => n,Hangup()
    same => n(menu),Goto(_X.,menu)
    same => n,Hangup()
    same => n(error),Playback(ivr/script-error)
    same => n,Hangup()
    
    ; Reverse numbers
    exten => 2,1,Read(SWAPTO,ivr/enter-dest-number,4,,1,5)
    same => n,GotoIf($["${SWAPTO}" = ""]?menu)
    same => n,Playback(ivr/entered-number)
    same => n,SayDigits(${SWAPTO})
    same => n,Read(APPROVE,ivr/press-1-for-accept,1,,1,5)
    same => n,GotoIf($["${APPROVE}" != "1"]?menu)
    same => n,Set(CHGNM=${SHELL(/usr/local/etc/asterisk/scripts/togglenumbers.sh ${CALLERID(num)} ${SWAPTO})})
    same => n,NoOp(${CHGNM})
    same => n,GotoIf($["${CHGNM}" = "GETERR"]?geterr)
    same => n,GotoIf($["${CHGNM}" = "SETERR"]?seterr)
    same => n,GotoIf($["${CHGNM}" = "OK"]?ok)
    same => n(ok),Playback(ivr/prov-saved&privacy-thankyou)
    same => n,System(/usr/local/etc/asterisk/scripts/rebuildlist.sh)
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/rebuild-ok)
    same => n,System(/usr/local/sbin/asterisk -rx "sip notify cisco-check-cfg ${CALLERID(num)} ${SWAPTO}")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/send-phone-reboot-ok)
    same => n,System(/usr/local/sbin/asterisk -rx "reload")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/server-reload-ok)
    same => n,Hangup()
    same => n(geterr),Playback(ivr/script-get-error)
    same => n,Hangup()
    same => n(seterr),Playback(ivr/script-set-error)
    same => n,Hangup()
    same => n(error),Playback(ivr/script-error)
    same => n,Hangup()
    same => n(menu),Goto(_X.,menu)
    same => n,Hangup()
    
    exten => 3,1,System(/usr/local/etc/asterisk/scripts/rebuildlist.sh)
    same => n,NoOp(${SYSTEMSTATUS})
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/rebuild-ok)
    same => n,System(/usr/local/sbin/asterisk -rx "reload")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/server-reload-ok)
    same => n,Goto(_X.,menu)
    same => n(error),Playback(ivr/script-error)
    same => n,Goto(_X.,menu)
    
    exten => 4,1,System(/usr/local/sbin/asterisk -rx "sip notify cisco-check-cfg ${CALLERID(num)}")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/send-phone-reboot-ok)
    same => n,System(/usr/local/sbin/asterisk -rx "reload")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/server-reload-ok)
    same => n,Hangup()
    same => n(error),Playback(ivr/script-error)
    same => n,Goto(_X.,menu)
    same => n,Hangup()
    
    exten => 5,1,System(/usr/local/sbin/asterisk -rx "reload")
    same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
    same => n,Playback(ivr/server-reload-ok)
    same => n,Goto(_X.,menu)
    same => n(error),Playback(ivr/script-error)
    same => n,Goto(_X.,menu)
    same => n,Hangup()
    


    Собственно что делает этот набор букв и цифр.
    1. Смена номера.
    Можно поменять существующий зарегистрированный рабочий номер на другой. Запрашивается номер телефона на который нужно поменять. После смены номера пересоздаётся список динамической конфигурации, делается reload на сервере и телефон автоматически перезагружается.
    2. Поменять телефоны местами
    Вызов осуществляется с телефона на который нужно поменять с другим телефоном. При замене телефонов внутри базы меняется информация отдаваемая конфигурационным скриптом. Логин пароль телефона не трогается, меняется только информация о callerid и ext.
    После смены номеров пересоздаётся список динамической конфигурации, делается reload на сервере и телефоны автоматически перезагружаются.
    3. Пересоздать список телефонов.
    Пересоздаются файлы динамической конфигурации. Удобно использовать когда много незарегистрированных телефонов в автоконфигурации стоят и ждут своей очереди в обслуживании, но не могут зарегистрироваться на сервере. Выполняется с любого телефона.Во время выполнения пересоздаётся список динамической конфигурации, делается reload на сервере.
    4. Перезагрузка аппарата
    Перезагружает аппарат с которого осуществлялся вызов. Автоматически делает reload на сервере.
    5. Обновление конфига на сервере.
    Автоматически делает reload на сервере.

    В общем-то всё. При корректном переносе скриптов на сервер, поднятие телефонии становится лёгким занятием. Изначально можно просто создать dialplan и влить его в базу. Дальнейшие действия вполне прозрачны и понятны любому человеку, даже не знакомому с администрированием asterisk. Будут вопросы — пишите. Схема вполне себе переносима на любую другую базу данных которую Вы больше любите.

    Aborche 2013
    • +17
    • 39,9k
    • 9
    Поделиться публикацией

    Комментарии 9

      0
      interface fastethernet1
      switchport trunk allowed vlan add 204
      switchport trunk native vlan 214
      exit

      <Enable_CDP ua=«na»>No</Enable_CDP>
      <Enable_LLDP-MED ua=«na»>No</Enable_LLDP-MED>

      После полной инициализации конфига, телефон переведёт порт компьютера в 204 VLAN

      Странная какая-то конфигурация… Я, правда, никогда не сталкивался со small business линейкой цискиных свитчей/телефонов, но вообще-то принято тегировать voice vlan и нейтивом пускать data vlan. Причем делается все проще (на каталистах, и наверняка на SB):

      interface fastethernet1
      switchport mode access
      switchport access vlan 204
      switchport voice vlan 214

      Кроме того, принято перемещать телефон в целевой VLAN с помощью CDP/LLDP (касается не только цискиных телефонов, но и Nortel, Avaya и так далее). Телефон сразу же после получения CDP или LLDP пакета понимает, что свой трафик тегируем в данном случае 214-м VLANом, а все от компьютера пойдет нейтивом, который — 204. Более того — если телефон вещает CDP/LLDP, очень удобно прямо на свитче узнать, кто на каком порту висит. Никаких настроек не требуется — только включить CDP/LLDP и настроить порт по указанному выше примеру.

      В вашей схеме, если воткнуть компьютер в свитч без телефона, он сразу попадет в voice vlan, что ни в какие ворота. Именно поэтому как раз data vlan без тега пускают.
        0
        300 серия отличается от каталистов как марс от венеры.
        Имея в наличии пачку деллов, обнаружил что firmware на 300 серии практически один в один с деллами совпадает, за исключением нескольких фич. Обновление, настройка бута и прочее вообще под копирку.
        А так вот настройки порта.
        Zone6Dev-SF300-24P(config-if)#switchport
          access               vlan unaware port
          customer             The port is connected to customer equipment
          default-vlan         default vlan
          forbidden            forbidden
          general              Configure switchport in general mode
          mode                 port mode
          protected-port       isolate layer 2 traffic from other protected ports
          trunk                vlan aware port
        


        Посему и конфиг такой в посте.
          0
          Что SB никакого отношения к IOS и каталистам не имеют я в курсе, но за все время, пока линейка выпускается, могли бы и унифицировать хотя бы внешне…

          3 минуты на гугл, и:
          supportforums.cisco.com/servlet/JiveServlet/download/3369247-87189/LLdpSetup.docx

          Да, надо лишние настройки сделать, но в итоге телефон скушает voice vlan по LLDP, а не по DHCP и уж тем более не по конфигу…

          И в любом случае, настоятельно рекомендую все-таки именно voice vlan пускать с тегом, а не наоборот.
            0
            Это понятно.
            спс за ссылку и рекомендации.
            Но не везде циски живут в сети, посему приходится так.
            Там где доступно попробую как время появится.
              0
              Но не везде циски живут в сети

              Причем тут циски-то? Сейчас все телефоны и все свитчи поддерживают нечто подобное. LLDP — открытый стандарт.
                0
                хм. действительно мои старые железки умеют. соберу сегодня лабораторию.
                  0
                  Собрал, посмотрел. lldp на моих коммутаторах в зачатке, максимум что может делать это анонсить себя соседним железкам, да и то криво. Nexus 3048 видит их через одно место.

                  Core-10G-1Box-5Floor# sh lldp neighbors interface ethernet 1/3 detail
                  
                  Chassis id: 0018.8b99.e06d
                  Port id: g3
                  Local Port id: Eth1/3
                  Port Description: not advertised
                  System Name: not advertised
                  System Description: not advertised
                  Time remaining: 110 seconds
                  System Capabilities:
                  Enabled Capabilities:
                  Management Address:
                  Vlan ID: 0
                  
                  Total entries displayed: 1
                  Core-10G-1Box-5Floor# sh lldp neighbors interface ethernet 1/3 system-detail
                  Capability codes:
                    (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device
                    (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other
                  Device ID   Local Intf  Chassis ID        Port ID        Hold-time   Capability
                  -           Eth1/3      0018.8b99.e06d    g3                120
                  
                  

                  При этом с другой стороны коммутатору вообще рвёт крышу. Port 51 = g3.
                  Dell3448_1Box_5Floor_21# sh lldp neighbors
                  
                  
                   Port       Device ID          Port ID          System Name     Capabilities
                  ------- ----------------- ----------------- ------------------- ------------
                    51    a4:93:4c:80:c7:aa      Eth1/3       436f72652d3130472d3      R
                                                              1426f782d35466c6f6f
                                                              7200
                  
                  Dell3448_1Box_5Floor_21# sh lldp neighbors ethernet
                    PORT                 IEEE 802.3 Ethernet port
                  Dell3448_1Box_5Floor_21# sh lldp neighbors ethernet g3
                  
                  Device ID: a4:93:4c:80:c7:aa
                  Port ID: Eth1/3
                  Capabilities: R
                  System Name: 436f72652d3130472d31426f782d35466c6f6f7200
                  System description: 436973636f204e65787573204f7065726174696e672053797374656d20284e582d4f532920536f6674776172650a54414320737570706f72743a20687474703a2f2f7777772e636973636f2e636f6d2f7461630a436f707972696768742028632920323030322d323058582c20436973636f2053797374656d732c20496e632e20416c6c207269676874732072657365727665642e
                  Port description: 446f776e6c696e6b5f746f5f31302e362e3230302e323100
                  Management Address: 10.6.200.100
                  
                  


                  Так что как в анекдоте «Были бы спички, был бы рай»
                    0
                    Так может, софт стоит обновить? Или уже EOL?

                    Деллы например тоже все поддерживают: en.community.dell.com/techcenter/networking/w/wiki/2587.powerconnect-35xx-lldp-med-configuration.aspx
                      0
                      в 3548 оно есть, в увы 3448 еще нет, хотя firmware последняя, eol у 3448 был в Августе 2011. Свои деньги они отработали на 5+ (с 2005 года по сей день).
                      Сейчас потихоньку мигрируем на Cisco.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое