Авто-генерація конфігураційних файлів телефонів Cisco

Для компаній, що використовують телефони Cisco в середовищі Asterisk, існує проблема зберігання десятків або сотень конфігураційних файлів для кожного телефону. На хвилі необхідності оновлення 30 телефонів (частково за прошивка, частково налаштуванням) я вирішив запропонувати технологію автоматичної генерації конфігураційних файлів.

Відразу треба сказати, що мова йде скоріше про технології, ніж про конкретної реалізації — код ще сирий і погано налагоджений. У цій статті передбачається, що ви вже конфигурировали телефони Cisco і знайомі з принципом їх роботи, як це, наприклад, пропонується на voip-info.org і подібних ресурсах.

Отже, трохи настановних даних:

Цискофоны бувають багатьох видів, але тут мова йде про телефони, які працюють по технології Cisco Call Manager (CCM) і тільки. Чому саме так — вони самі приємні у використанні як для користувача, так і з адмінської боку.

Звичайний процес завантаження і роботи телефону з моменту включення виглядає так:

  • отримання IP-адреси;
  • завантаження конфігураційного файлу з TFTP-сервера (з назвою виду SEP<MAC>.cnf.xml;
  • скачування різних файлів, згаданих у конфігурації, в т. ч. прошивки;
  • реєстрація в Asterisk.
Конфігураційний файл досить великий і складний, визначає практично всі аспекти роботи телефону, що і викликає певний інтерес.

Припустимо TFTP-сервер це наше власне, тоді в момент віддачі конфігурації TFTP-сервер міг би її створити, якщо б знав настановні параметри. У мінімальному варіанті список параметрів такий:

  • логін/пароль пристрою в Asterisk;
  • прізвище працівника у якого стоїть телефон;
  • назва файлу прошивки телефону і марку телефону, якщо конфігурації будуть відрізнятися.
Все це звичайно є в наших каталогах крім прошивки.

1. Отримання IP-адреси

Щоб дізнатися що за прошивку треба запропонувати — теж треба знати марку. Марку свою телефон говорить у двох випадках — у полі 60 DHCP запиту і при реєстрації SIP. При реєстрації це вже пізно, значить ми будемо брати марку з DHCP-запиту.

У dhcpd.conf можна прописати наступне:

on commit {
set clip = binary-to-ascii(10, 8, ".", leased-address);
set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
execute("/root/bin/dhcpevent.php", "commit", clip, clhw, option vendor-class-identifier);
}

Це скаже DHCP-сервера, щоб при видачі lease він викликав скрипт приблизно такого вигляду:

/root/bin/dhcpevent.php commit 172.20.21.209 0:f:77:12:bc:aa "Cisco Systems, Inc. IP Phone CP-7945G"

Тут нам треба десь зберегти марку, щоб при запиті конфігурації на неї спертися. Зберігати її можна де завгодно — в БД, в БД Asterisk або в текстовому файлі. Я вибрав варіант зберігати її у LDAP поряд з іншими обліковими даними і використовую для цього непотрібний атрибут telexNumber. Щоб призначати людям телефони я додав їм клас ieee802device і приписав атрибут macAddress у вигляді MAC-адреси телефону.

dhcpevent.php
#!/usr/local/bin/php
<?php

$rdn = 'uid=root,ou=Users,dc=labma,dc=ru'; // DN to auth against LDAP
$pass = 'superpass'; // Password

$cont = "telexNumber"; // Attribute to fill with Cisco phone ID

$ds = ldap_connect("pilot.labma.ua");

// Exit if not connected
if (!$ds)
exit (128);

// Modern LDAP do not work on v1/v2
if (!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3))
exit (128);

// That means phone is not for us
if (!preg_match ("/^Cisco/", $argv[4]))
exit (1);

$r = ldap_bind($ds, $rdn, $pass);

$mac = "";
$macar = explode (":", $argv[3]);

if (count($macar) != 6) 
exit (128);

// PHP LDAP client get keys in low
$contl = strtolower($cont);

// DHCP server not send padded MAC
foreach ($macar as $byte) 
$mac .= str_pad ($byte, 2, 0, STR_PAD_LEFT);

$sr = ldap_search(
$ds, 
"dc=labma, dc=ru", 
"macAddress=$mac", 
array ("dn", $cont)
); 

if (ldap_count_entries($ds, $sr) != 1)
exit (4);

$info = ldap_get_entries($ds, $sr)[0];

if ((array_key_exists($contl, $info)) && ($argv[4] == $info[$contl][0]))
exit (0);

$res = ldap_mod_replace (
$ds, 
$info["dn"], 
array ($cont => $argv[4])
);

if (!$res)
exit (128);

ldap_close ($ds);

exit (0);

?>

Скрипт шукає в LDAP користувача з мак-адресою, який повідомив dhcpd і проставляє йому telexNumber, якщо він ще не проставлено.

2. Отримання конфігурації

Цікаво, що цискофоны спочатку звертаються на сервер TFTP по порту 6970, протокол HTTP, а вже потім йдуть як зазвичай. Дуже рекомендую замінити у себе в компанії сервер TFTP на HTTP/6970 — завантаження телефону прискориться. Важливо! Якщо сервер відповість, але поверне 404 або 500 — TFTP запитуватися не буде і телефон не завантажиться. TFTP нормально працює, якщо 6970 не відповідає взагалі. Гірше всього, якщо порт заблокований — завантаження сповільнюється в рази.

Робимо VirtualHost *:6970
<VirtualHost *:6970>
ServerAdmin webmaster@pbx.labma.ru
DocumentRoot "/export/tftp"
</VirtualHost>

<Fs "/export/tftp/">
Options Indexes FollowSymLinks
AllowOverride None
Require ip 172.20.21.0/24
</Directory>

і .htaccess:

RewriteEngine On
RewriteRule ^(.*)\.xml$ index.php [L]


А /export/tftp кладемо власне index.php

<?php

if (preg_match ("/\SEP(\w+).cnf.xml/", $_SERVER["REQUEST_URI"], $m))
$mac = $m[1];
else {
$file = getcwd ().$_SERVER["REQUEST_URI"];

if (!file_exists ($file))
_fail();

header ("Content-type: text/xml");
header ('Content-Length:' . filesize($file));
readfile ($file);
exit (0);
}

$user = _getUser($mac);

if (!$user)
_fail();

$tmpl = "template.".$user["cisco"].".xml";

if (!file_exists ($tmpl))
_fail ();

$xml = file_get_contents ("template.".$user["cisco"].".xml");

// getLoadA hardcoded, loadB - search directory
$user["load"] = _getLoadA($user["cisco"]);

foreach ($user as $key = > $value) {
$xml = preg_replace ("/\#\#$key\#\#/m", $value, $xml);
}

header ("Content-type: text/xml");
header ('Content-Length:' . strlen($xml));

echo $xml;

exit;

function _getUser ($mac) {
$rdn = 'uid=root,ou=Users,dc=labma,dc=ru'; // DN to auth against LDAP
$pass = 'superpassword'; // Password

$cont = "telexNumber"; // Attribute to fill with Cisco phone ID

$ds = ldap_connect("pilot.labma.ua");

// Exit if not connected
if (!$ds)
exit (128);

// Modern LDAP do not work on v1/v2
if (!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3))
exit (128);

$r = ldap_bind($ds, $rdn, $pass);

$sr = ldap_search(
$ds, 
"dc=labma, dc=ru", 
"macAddress=$mac"
); 
if (ldap_count_entries($ds, $sr) != 1) {
return null;
}

$info = ldap_get_entries($ds, $sr)[0];

$user = array();

$user ["label"] = $info["sn"][0];

$user ["phone"] = $info["uidnumber"][0];

if (preg_match ("/CP-(\d+)/", $info["telexnumber"][0], $m))
$user ["cisco"] = $m[1];
else
return null;

return $user;
}

function _getLoadA($cisco) {

$list = array (
3951 => "SIP3951.8-1-4a",
7906 => "SIP11.9-4-2SR1-1S",
7911 => "SIP11.9-4-2SR1-1S",
7931 => "SIP31.9-4-2SR2-2S",
7941 => "SIP41.9-4-2SR2-2S",
7945 => "SIP45.9-4-2SR2-2S",
7961 => "SIP41.9-4-2SR2-2S",
7965 => "SIP45.9-4-2SR2-2S",
8941 => "SIP894x.9-4-2SR3-1",
8845 => "sip8845_65.11-5-1SR1-1",
8865 => "sip8845_65.11-5-1SR1-1",
);

if (!array_key_exists ($cisco, $list))
return "";

if (!file_exists (getcwd()."/".$list["cisco"].".loads"))
return "";

return $list[$cisco];
}

function _getLoadB($cisco) {

$list = array (
3951 => "SIP3951",
7906 => "SIP11",
7911 => "SIP11",
7931 => "SIP31",
7941 => "SIP41",
7945 => "SIP45",
7961 => "SIP41",
7965 => "SIP45",
8941 => "SIP894x",
8845 => "sip8845_65",
8865 => "sip8845_65",
);

if (!array_key_exists ($cisco, $list))
return "";

$files = glob ($list[$cisco].".*.loads");

if (count($files) != 1)
return "";
else
return str_replace (".loads", "", $files[0]);
}

function _fail () {
header ("HTTP/1.0 404 Not Found");
exit (0);
}
?>

Таким чином картина наступна — телефон запитує свій файл SEP<MAC>, видирає звідси мак-адресу, запитуємо у LDAP марку та інші параметри для телефону, відкриваємо шаблон виду template.7941.xml змінюємо в ньому змінні і віддаємо телефону. Якщо це не xml — файл віддасть сам Apache, якщо це xml але не для нас — віддаємо його самі, якщо мак адресу або шаблон не знайдено телефоном 404.

Виклик функції _getLoadA можна замінити на _getLoadB і прошивки будуть шукатися в каталозі, у першому ж випадку їх назви статично вбиті в код.

Раніше мені хотілося зробити складні шаблони із загальних частин і так далі, але зараз я такої необхідності не бачу і для всіх телефонів крім 3951 один шаблон.

Тепер достатньо просто розставити користувачам у LDAP MAC-адреси телефонів і все.

3. Небезпеки

  • Якщо порт 6970 виразно відповідає — TFTP запитуватися не буде
  • Телефони з прошивкою SIP 8.x не запитують порт 6970
  • TCP і UDP телефони одночасно працювати в одному Asterisk не можуть — слідкуйте за прошивками і тегом transportLayer
  • DHCP-сервер віддає мак без незначущих нулів
  • LDAP сервер шукає по MAC без урахування регістру
Джерело: Хабрахабр

0 коментарів

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