Глава 20

Распределённое универсальное распознавание номеров (DUNDi)

Общество подобно кораблю; каждый должен быть

готов принять штурвал.

—Генрик Ибсен

Distributed Universal Number Discovery, или DUNDi (Распределенное универсальное распознавание номеров) — это протокол обнаружения служб, который можно использовать для поиска ресурсов в удаленных местоположениях. Первоначальное намерение DUNDi состояло в том, чтобы разрешить децентрализованную маршрутизацию между многими узлами с использованием General Peering Agreement (GPA — общее соглашение о пиринге). GPA призван взять на себя роль централизованного органа управления документацией для создания доверительных отношений между пирами в облаке. Хотя идея интересная и здравая, GPA еще не взлетел. Это не означает, что сам протокол DUNDi не нашел применения: первоначальное намерение DUNDi было расширено, так что теперь он действует не только как служба определения местоположения, а может использоваться для запроса и передачи информации среди пиров.

Как работает DUNDi?

Думайте о DUNDi как о большой телефонной книге, которая позволяет вам спрашивать коллег, знают ли они об альтернативном маршруте VoIP к добавочному номеру или номеру телефона PSTN.

Например, предположим что вы подключены к другому набору из блоков Asterisk, слушаете и отвечаете на запросы DUNDi, и те блоки, которые в свою очередь подключены к другим блокам Asterisk слушают и отвечают на запросы DUNDi. Предположим также, что ваша система не имеет прямого доступа для запроса чего-либо с удаленных серверов.

Рисунок 23-1 иллюстрирует, как работает DUNDi. Вы спрашиваете своего друга Боба, знает ли он, как достичь внутреннего номера 4001, к которому у вас нет прямого доступа. Боб отвечает: “Я не знаю, как добраться до этого номера, но позвольте мне спросить моего пира Салли.”

Рисунок 23-1. Система запросов DUNDi peer-to-peer

Боб спрашивает Салли, знает ли она, как достичь запрошенного внутр.номера, и она отвечает: “Вы можете достичь этого номера в IAX2/dundi:very_long_password@hostname/extension.” Затем Боб сохраняет адрес в своей базе данных и передает вам информацию о том, как добраться до 4001. С новообретенной информации, вы можете сделать отдельный запрос, чтобы на самом деле разместить вызов к боксу Салли, чтобы достичь номера 4001. (DUNDi только помогает вам найти информацию, в которой вы нуждаетесь для соединения; но фактически не совершает вызов.)

Поскольку Боб сохранил найденную информацию, он сможет предоставить ее всем коллегам, которые позже запросят у него тот же номер, так что поиск не будет идти дальше. Это помогает уменьшить нагрузку на сеть и уменьшает время отклика для номеров, которые часто просматриваются. (Однако следует отметить, что DUNDi создает сменяющийся ключ, и таким образом сохраненная информация действительна в течение ограниченного периода времени.)

DUNDi выполняет поиск динамически, либо с помощью оператора switch => в вашем файле extensions.conf или с использованием функции диалплана DUNDILOOKUP().

В то время как DUNDi был первоначально разработан и предназначен для использования в качестве пиринговой матрицы для ТфОП, он чаще всего используется в частных сетях.1 Если вы являетесь администратором Asterisk на крупном предприятии (или даже установки с парой блоков Asterisk в разных физических расположениях), то можете упростить администрирование добавочных номеров. DUNDi — это фантастический инструмент для этого, потому что он позволяет вам просто делиться внутренними номерами, которые были настроены в каждом местоположении динамически, запрашивая номера из удаленного местоположения, когда ваш локальный блок не знает, как их достичь.

Кроме того, если в одной локации есть более дешевый маршрут к номеру ТфОП, который вы хотели набрать, то можете запросить маршрут в DUNDi облаке. Например, если один блок находится в Ванкувере, а другой — в Торонто, то Ванкуверский офис может отправлять звонки, предназначенные для района Торонто, по сети с помощью VoIP и из PRI в Торонто, чтобы они могли быть размещены локально в ТфОП. Аналогичным образом, отделение в Торонто могло бы осуществлять звонки, предназначенные для Ванкувера, из PRI отделения в Ванкувере.

Файл dundi.conf

Часто бывает полезно ознакомиться с опциями, доступными нам, до того, как углубиться в файл конфигурации, но не стесняйтесь пропустить этот раздел сейчас и вернуться по ссылкам на конкретные параметры после того, как вы получили свою первоначальную конфигурацию и работаете.

В dundi.conf есть три секции: раздел [general], раздел [mappings] и определения пиров, такие как [FF:FF:FF:FF:FF:FF]. Мы покажем варианты, доступные для каждого раздела В отдельных таблицах.

В Таблице 23-1 перечислены параметры, доступные в разделе [general] dundi.conf.

Таблица 23-1. Параметры, доступные в разделе [general]

Опция Описание
department Используется при запросе контактных данных удаленной системы. Примером может служить Communications.
organization Используется при запросе контактных данных удаленной системы. Примером может быть ShiftEight.org.
locality Используется при запросе контактных данных удаленной системы. Примером может быть Toronto.
stateprov Используется при запросе контактных данных удаленной системы. Примером может быть Ontario.
country Используется при запросе контактных данных удаленной системы. Примером может быть Canada.
email Используется при запросе контактных данных удаленной системы. Примером может быть [email protected]
phone Используется при запросе контактных данных удаленной системы. Примером может быть +1-416-555-1212
binaddr Используется для управления IP-адресом, к которому будет привязываться система. Это может быть только IPv4-адрес. Значение по умолчанию — 0.0.0.0, что означает, что система будет слушать (и отвечать) на всех доступных интерфейсах.
port Порт для прослушивания запросов. Значение по умолчанию — 4520.
tos Значение условия обслуживания или качества обслуживания (ToS/QoS), которое будет использоваться для запросов. Дополнительные сведения о доступных значениях и способах их использования см. в wiki.
entityid ID объекта системы. Должен быть внешний (сетевой) MAC-адрес. Формат 00:00:00:00:00:00.
cachetime Как долго пиры должны кэшировать наши ответы, в секундах. По умолчанию 3600.
ttl Время жизни или максимальная глубина поиска ответа в сети. Максимальное время ожидания ответа рассчитывается с использованием (2000 + 200 * ttl) мс.
autokill Используется для контроля того, как долго мы ждем ACK для нашего DPDISCOVER. Установка этого тайм-аута предотвращает остановку поиска из-за скрытого пира. Он может быть yes, no или числовое значение, представляющее количество миллисекунд ожидания. Вы можете использовать параметр qualify, чтобы включить его для каждого пира.
secretpath Сменяющийся ключ создается и хранится в AstDB. Значение хранится в ключе ‘secret’ под семейством, определенным secretpath. По умолчанию secretpath — dundi, в результате чего ключ будет храниться по умолчанию в dundi/secret.
storehistory Используется для указания, следует ли хранить в памяти историю последних нескольких запросов, а также сколько времени заняли запросы. Допустимые значения-yes и no (также доступны с помощью команд cli dundi store history и dundi no store history). Это средство отладки, которое по умолчанию отключено из-за возможных воздействий на производительность.

В Таблице 23-2 перечислены параметры, которые можно настроить в разделе [mappings] dundi.conf.

Таблица 23-2. Параметры, доступные в разделе [mappings]

Опция Описание
nounsolicited Используется для рекламы без нежелательных звонков на возвращаемый результат. Применяется в публичных сетях.
nocomunsolicit Используется для рекламы, некоммерческих нежелательных звонков на возвращаемый результат. Применяется в публичных сетях.
residential Используется для определения маршрута, возвращаемого в качестве жилого местоположения. Применяется в общественных сетях.
commercial Используется для определения маршрута, возвращаемого как коммерческое местоположение. Применяется в общественных сетях.
mobile Используется для определения маршрута, возвращаемого как мобильный телефон. Применяется в общественных сетях.
nopartial Используется для предотвращения частичного поиска номеров, выполняемого для этого сопоставления.
${NUMBER} Переменная, содержащая значение просматриваемого запроса.
${IPADDR} Переменная, содержащая IP-адрес локальной системы. Может использоваться для динамического построения ответов сопоставления. Не рекомендуется.
${SECRET} Переменная, содержащая значение сменяющегося секретного ключа, как определено в secretpath в AstDB.

Наконец, в Таблице 23-3 перечислены параметры, доступные в разделах пиров dundi.conf.

Таблица 23-3. Параметры, доступные для определений пиров в dundi.conf

Опция Описание
inkey Входящий ключ аутентификации.
outkey Ключ, используемый для проверки подлинности удаленного узла.
host Имя хоста или IP-адрес удаленного узла.
port Порт, по которому можно связаться с удаленным узлом.
order Порядок поиска, связанный с этим узлом. Значения включают primary, secondary, tertiary и quartiary. Будет искать только первичные пиры, если они недоступны — в этом случае будут искать вторичные и т.д.
include Используется для управления включением этого пира в поиск определенного сопоставления. Может быть установлено значение all, если используется для всех сопоставлений.
noinclude Используется для управления исключениями этого пира из поиска определенного сопоставления. Может быть установлено all, если этот узел должен быть исключен из поиска.
permit Используется для управления возможностью выполнения пиром поиска по определенным сопоставлениям. Если значение равно all, этот пир может выполнять поиск по всем определенным сопоставлениям.
deny Используется для контроля того, какие сопоставления этому узлу запрещено искать. Значение может быть установлено на all, чтобы ограничить этому узлу возможность выполнять любые поиски по определенным сопоставлениям.
model Используется для управления тем, может ли этот пир получать запросы (inbound), передавать запросы (outbound) или выполнять и то и другое (symmetric).
precache Обычно используется, когда у нас есть узел с несколькими маршрутами, который хочет передать эти значения на другой узел, предоставляющий больше ответов (это известно как предварительное кэширование, предоставление ответа, когда запрос не был получен). Значения включают outgoing, incoming и symmetric. Если установлено в outgoing — мы прокладываем маршруты к этому узлу. При значении incoming мы получаем маршруты от этого узла. Если задано symmetric мы делаем и то и другое.

Настройка Asterisk для использования с DUNDi

Есть три файла, которые необходимо настроить для DUNDi: dundi.conf, extensions.conf и sip.conf.2 Файл dundi.conf контролирует аутентификацию пиров, которым мы разрешаем выполнять поиск через нашу систему. Этот файл также управляет списком пиров, которым мы можем отправить наши собственные запросы поиска. Поскольку можно запускать несколько разных сетей на одном и том же блоке, необходимо определить разные секции для каждого узла, а затем настроить сети, в которых этим пирам разрешено выполнять поиск. Кроме того, нам нужно определить, какие пиры мы хотим использовать для выполнения поиска.

Общая конфигурация

В разделе [general] в dundi.conf содержатся параметры, относящиеся к общей работе клиента и сервера DUNDi:

;Файл конфигурации DUNDi для Toronto

;

[general]

;

department=IT

organization=toronto.shifteight.org

locality=Toronto

stateprov=ON

country=CA

[email protected]

phone=+14165551212

;

; Укажите адрес привязки и номер порта. По умолнчанию порт 4520.

;bindaddr=0.0.0.0

port=4520

entityid=FF:FF:FF:FF:FF:FF

ttl=32

autokill=yes

;secretpath=dundi

Идентификатор объекта, определенный в entityid, обычно является MAC-адресом интерфейса на компьютере. ID объекта по умолчанию является первым Ethernet-адресом сервера, но вы можете переопределить его с помощью entityid, если он установлен на MAC-адрес некоторого вашего устройства. Рекомендуется использовать MAC-адрес основного внешнего интерфейса. Этот адрес будет использоваться другими узлами для идентификации.

Поле time-to-live (ttl) определяет, на сколько прыжков удалены пиры, от которых мы получаем ответы, и используется для прерывания цикла. Каждый раз, когда запрос передается по линии, которой запрошенный номер не известен, значение в поле TTL уменьшается на единицу, подобно полю TTL пакета ICMP. Поле TTL определяет максимальное количество секунд, в течение которого мы готовы ждать ответа.

Когда вы запрашиваете поиск номера, исходный запрос (называемый DPDISCOVER) отправляется вашим пирам, запрашивающим этот номер. Если вы не получили подтверждение (ACK) вашего запроса (DPDISCOVER) в течение 2000 мс (достаточное время только для одной передачи) и autokill установлен в yes, Asterisk отправит пирам CANCEL. (Обратите внимание, что подтверждение не обязательно является ответом на запрос; это просто подтверждение того, что пир получил запрос.) Цель autokill — предотвратить остановку поиска из-за хостов с высокой задержкой. В дополнение к параметрам yes и no, вы также можете указать количество миллисекунд ожидания.

Модуль pbx_dundi создает сменяющийся ключ и сохраняет его в локальной базе данных Asterisk (AstDB). secret — имя ключа хранится в семействе dundi. Значение ключа можно просмотреть с помощью команды database show в консоли Asterisk. Семейство баз данных можно переопределить с помощью параметра secretpath.

Нам нужен другой пир для взаимодействия, поэтому вот конфигурация для другого узла:

; Файл конфигурации DUNDi для Vancouver

;

[general]

;

department=IT

organization=vancouver.shifteight.org

locality=Vancouver

stateprov=BC

country=CA

[email protected]

phone=+16135551212

;

; Укажите адрес привязки и номер порта. По умолнчанию порт 4520.

;bindaddr=0.0.0.0

port=4520

entityid=00:00:00:00:00:00

ttl=32

autokill=yes

;secretpath=dundi

В следующем разделе мы создадим наши начальные узлы DUNDi.

Первоначальное определение узлов DUNDi

Узел DUNDi идентифицируется уникальным MAC-адресом 2 уровня интерфейса удаленной системы. Файл dundi.conf — это место, где мы определяем, в каком контексте искать пиры, запрашивающие поиск, и какие пиры мы хотим использовать при поиске для конкретной сети. Следующая конфигурация определена в dundi.conf в нашей системе Торонто:

[00:00:00:00:00:00] ; Удаленный офис Vancouver

model = symmetric

host = vancouver.shifteight.org

inkey = vancouver

outkey = toronto

qualify = yes

dynamic=yes

Идентификатор удаленного узла (MAC-адрес) заключен в квадратные скобки ([]). inkey и outkey -это пары открытого и закрытого ключей, которые мы используем для аутентификации. Пары ключей генерируются с помощью скрипта astgenkey, расположенного в каталоге исходников ~/src/asterisk-complete/asterisk/11/contrib/scripts. Мы используем флаг -n, чтобы не инициализировать пароли при каждом запуске Asterisk:

$ cd /var/lib/asterisk/keys

$ sh ~/src/asterisk-complete/asterisk/11/contrib/scripts/astgenkey -n toronto

Мы разместим полученные ключи — toronto.pub и toronto.key в нашем каталоге /var/lib/asterisk/keys. Файл toronto.pub — это открытый ключ, который мы разместим на веб-сервере, чтобы он был легко доступен для всех, с кем мы хотим соединяться. Когда мы сверяем, то можем дать нашим пирам открытый ключ, доступный по HTTP, который они могут поместить в свои каталоги /var/lib/asterisk/keys (используя что-либо наподобие wget).

На блоке Vancouver мы будем использовать следующую конфигурацию пира в dundi.conf:

[FF:FF:FF:FF:FF:FF] ; Удаленный офис Toronto

model = symmetric

host = toronto.shifteight.org

inkey = toronto

outkey = vancouver

qualify = yes

dynamic=yes

Затем мы выполним тот же скрипт astgenkey на блоке Vancouver для создания открытых и закрытых ключей vancouver. Наконец, мы разместим ключ toronto.pub на сервере Ванкувера в /var/lib/asterisk/keys и поместим vancouver.pub на сервере Toronto в том же месте.

После загрузки ключей мы должны перезагрузить модули res_crypto.so и pbx_dundi.so в Asterisk:

toronto*CLI> module reload res_crypto.so

— Reloading module ‘res_crypto.so’ (Cryptographic Digital Signatures)

— Loaded PUBLIC key ‘vancouver’

— Loaded PUBLIC key ‘toronto’

— Loaded PRIVATE key ‘toronto’

vancouver*CLI> module reload res_crypto.so

— Reloading module ‘res_crypto.so’ (Cryptographic Digital Signatures)

— Loaded PUBLIC key ‘toronto’

— Loaded PUBLIC key ‘vancouver’

— Loaded PRIVATE key ‘vancouver’

Мы можем проверить ключи, поэтому мы знаем, что они готовы к загрузке в любое время с помощью команды CLI keys show:

*CLI> keys show

Key Name Type Status Sum

—————— ——— —————- ———————————

vancouver PRIVATE [Loaded] c02efb448c37f5386a546f03479f7d5e

vancouver PUBLIC [Loaded] 0a5e53420ede5c88de95e5d908274fb1

toronto PUBLIC [Loaded] 5f806860e0c8219f597f876caa6f2aff

3 known RSA keys.

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

*CLI> module reload pbx_dundi.so

— Reloading module ‘pbx_dundi.so’ (Distributed Universal Number

Discovery (DUNDi))

== Parsing ‘/etc/asterisk/dundi.conf’: Found

Иногда перезагрузка вызывает синхронизацию не так быстро, как мы хотели бы. Если вы столкнетесь с этим, то можете просто module unloadpbx_dundi.so с обеих сторон, а затем на обеих системах нужно сделать новый запуск module loadpbx_dundi.so.

Наконец, мы можем проверить, что системы успешно соединились с помощью dundi show peers:

toronto*CLI> dundi show peers

EID Host Port Model AvgTime Status

00:00:00:00:00:00 172.16.0.104 (S) 4520 Symmetric Unavail OK (3 ms)

1 dundi peers [1 online, 0 offline, 0 unmonitored]

Теперь, когда наши пиры настроены и доступны, нам нужно создать контексты сопоставления, которые будут контролировать, какая информация будет возвращена в поиске.

Создание контекстов сопоставления

Файл dundi.conf определяет контексты DUNDi, которые сопоставляются с контекстами диалплана в файле extensions.conf. Контексты DUNDi — это способ определения различных и отдельных групп служб каталогов. Контексты в разделе [mapping] указывают на контексты в extensions.conf, который управляет номерами, которые вы рекламируете.

Когда вы создаете пир, то нужно определить, какие контексты сопоставления вы позволите этому пиру искать. Это делается с помощью инструкции permit (каждый узел может содержать несколько инструкций permit). Контексты сопоставления связаны с контекстами диалплана в том смысле, что они являются границей безопасности для ваших пиров. Мы включим наше отображение в следующем разделе.

Все контексты отображения DUNDi принимают форму:

dundi_context => local_context,weight,technology,destination[,options]]

Следующая конфигурация создает контекст сопоставления DUNDi, который мы будем использовать для объявления наших локальных добавочных номеров группе. Мы добавим эту конфигурацию к dundi.conf в системе Toronto под заголовком [mappings]. Обратите внимание, что все это должно отображаться в одной строке:

[mappings]

; Все в одной строке

extensions => RegisteredDevices,0,SIP,dundi:very_secret_secret@ toronto.shifteight.org/

${NUMBER},nopartial

Конфигурация системы Vancouver будет выглядеть следующим образом:

[mappings]

; Всё в одной строке

extensions => RegisteredDevices,0,SIP,dundi:very_secret_secret@ vancouver.shifteight.org/

${NUMBER},nopartial

В этом примере контекст сопоставления — это extensions, указывающийе на контекст Registered Devices в extensions.conf (предоставление списка добавочных номеров для ответа: наша телефонная книга). Номера, которые разрешаются для УАТС, должны быть объявлены с нулевым weight (подключены напрямую). Числа выше нуля указывают на увеличение числа переходов или путей для достижения конечного пункта назначения. Это полезно когда несколько ответов для одного и того же поиска получены в конце, который первоначально запросил номер; путь с меньшим весом будет предпочтительным. Мы рассмотрим, как контролировать ответы в разделе “Контроль ответов”.

Если мы можем ответить на поиск, наш ответ будет содержать метод, с помощью которого другой конец можно подключиться к системе. Он включает в себя технологию для использования (например, IAX2, SIP, H.323 и т.д.), имя пользователя и пароль для аутентификации, на какой хост отправить аутентификацию, и, наконец, добавочный номер.

Asterisk предоставляет некоторые ярлыки, позволяющие нам создать ”шаблон», с помощью которого мы можем создавать наши ответы. Для построения шаблона можно использовать следующие переменные канала:

${SECRET}

Заменяется паролем, хранящимся в локальной AstDB. Используется только с iax.conf.

${NUMBER}

Запрашиваемый номер.

${IPADDR}

IP-адрес для подключения.

Обычно безопаснее всего статически настраивать имя хоста, а не использовать переменную ${IPADDR}. Переменная ${IPADDR} иногда отвечает адресом в частном IP-пространстве, который недоступен из интернета.

Настроив наше сопоставление, давайте создадим простой контекст диалплана, в котором мы можем выполнять поиск для тестирования. Мы сделаем его более динамичным в разделе «Контроль ответов«.

В extensions.conf, мы можем добавить следующее в обе системы:

[RegisteredDevices]

exten => 1000,1,NoOp()

С нашими настроенными диалпланом и сопоставлениями нам нужно загрузить их в память из CLI:

*CLI> dialplan reload

*CLI> module reload pbx_dundi.so

— Reloading module ‘pbx_dundi.so’ (Distributed Universal Number

Discovery (DUNDi))

== Parsing ‘/etc/asterisk/dundi.conf’: == Found

Мы можем проверить, что отображение было загружено в память с помощью команды dundi show mappings:

toronto*CLI> dundi show mappings

DUNDi Cntxt Weight Local Cntxt Options Tech Destination

extensions 0 RegisteredDe NONE SIP dundi:${SECRET}@172.16.0.

Наш простой диалплан и сопоставления настроены, нам нужно определить какие сопоставления каждому из наших пиров разрешено использовать. Мы сделаем это в следующем разделе.

Использование контекстов сопоставления с пирами

В наших определениях сопоставлений в файле dundi.conf мы должны дать нашим пирам разрешение на их использование. Управление различными сопоставлениями осуществляется через парметры permit, deny, include и noinclude в определении пиров. Мы используем permit и deny для управления разрешено ли удаленному узлу искать определенное сопоставление в нашей локальной системе. Мы используем include и noinclude для управления, какие узлы мы будем использовать для выполнения поиска в определенном сопоставлении.

Поскольку у нас есть только одно определенное отображение (extensions), мы собираем permit и include extensions в наши определения пиров как в системе Торонто, так и в Ванкувере.

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

[00:00:00:00:00:00] ; Удаленный офис Vancouver

model = symmetric

host = vancouver.shifteight.org

inkey = vancouver

outkey = toronto

qualify = yes

dynamic=yes

permit=extensions

include=extensions

Аналогично, мы включим permit и include сопоставление для extensions для офиса Торонто в системе Ванкувера:

[FF:FF:FF:FF:FF:FF] ; Удаленный офис Toronto

model = symmetric

host = toronto.shifteight.org

inkey = toronto

outkey = vancouver

qualify = yes

dynamic=yes

permit=extensions

include=extensions

После изменения пиров мы перезагружаем модуль pbx_dundi.so, чтобы изменения вступили в силу:

*CLI> module reload pbx_dundi.so

Конфигурацию include и permit можно проверить с помощью команды dundi show peer в CLI Asterisk:

*CLI> dundi show peer 00:00:00:00:00:00

Peer: 00:00:00:00:00:00

Model: Symmetric

Host: 172.16.0.104

Port: 4520

Dynamic: no

Reg: No

In Key: vancouver

Out Key: toronto

Include logic:

— include extensions

Query logic:

— permit extensions

Теперь мы можем проверить наши поиски. Мы можем сделать это легко из CLI Asterisk, используя команду dundi lookup. Если выполним поиск из системы Ванкувера, то получим ответ от системы Торонто с адресом, который можем использовать для вызова. Мы добавили ключевое слово bypass в конец поиска, чтобы обойти кэш (в случае, если хотим выполнить несколько тестов):

vancouver*CLI> dundi lookup 1000@extensions bypass

1. 0 SIP/dundi:very_secret_secret@172.16.0.161/1000 (EXISTS)

from ff:ff:ff:ff:ff:ff, expires in 3600 s

DUNDi lookup completed in 12 ms

Ответ SIP/dundi:very_secret_secret@172.16.0.161/1000 дает нам адрес, который мы можем использовать для вызова внутреннего номера 1000. (Конечно, мы не можем использовать этот адрес в данный момент, потому что мы не настроили никаких пиров в системе Торонто или Ванкувер для фактического приема вызова, но, по крайней мере, у нас есть часть поиска DUNDi, работающая сейчас!) В следующем разделе мы рассмотрим, как принимать вызовы в нашу систему после того, как нам ответили на запрос DUNDi.

Разрешение удаленных подключений

В файле sip.conf нам нужно включить пира, от которого мы можем принимать вызовы, и соответствующим образом обрабатывать вызовы этого пира в диалплане. Аутентификация выполняется с использованием пароля, как определено в сопоставлении в dundi.conf.

Если вы используете iax.conf, то можете использовать переменную ${SECRET} в сопоставлении вместо пароля, который динамически заменяется сменяющимся ключом и обновляется каждые 3600 секунд (1 час). Значение секретного ключа хранится в базе данных Asterisk и доступно с помощью параметра dbsecret в определении пира в iax.conf.

Вот определение пользователя для пользователя dundi, как определено в sip.conf:

[dundi]

type=user

secret=very_secret_secret

context=DUNDi_Incoming

disallow=all

allow=ulaw

allow=alaw

Запись context — DUNDi_Incoming — это место, где авторизованные абоненты входят в extensions.conf. Оттуда мы можем контролировать вызов так же, как и в диалплане любого другого входящего соединения.

Мы также можем использовать опции permit и deny для пира в sip.conf для управления IP-адресами, с которых будем принимать вызовы. Управление IP-адресами даст нам дополнительный уровень безопасности, если мы ожидаем вызовов только от известных конечных точек, например внутри нашей организации.

Обязательно перезагрузите chan_sip.so, чтобы включить вновь созданного пользователя в sip.conf:

toronto*CLI> sip reload

Чтобы принимать входящие вызовы определите контекст [DUNDi_Incoming] в extensions.conf и добавьте следующее в диалплан системы Toronto:

[DUNDi_Incoming]

exten => 1000,1,Verbose(2,Incoming call from the DUNDi peer)

same => n,Answer()

same => n,Playback(silence/1)

same => n,Playback(tt-weasels)

same => n,Hangup()

Перезагрузите диалплан с помощью dialplan reload после сохранения изменений в extensions.conf. Для нашего первого теста мы создадим номер в контексте LocalSets и попробуем позвонить на номер 1000, используя информацию, предоставленную через DUNDi:

[LocalSets]

exten => 1000,1,Verbose(2,Test extension to place call to remote server)

same => n,Dial(SIP/dundi:[email protected]/1000,30)

same => n,Hangup()

Если мы перезагрузим диалплан и попробуем протестировать номер, набрав 1000, мы должны быть подключены к подсказке tt-weasels на удаленной машине. С нашим пользователем, настроенным правильно для приема входящих вызовов, давайте сделаем наш диалплан и ответы более динамичными с некоторыми дополнительными инструментами.

Использование dbsecret с iax.conf

Если вы используете драйвер канала iax.conf, то можете аутентифицировать входящие вызовы, используя директиву dbsecret в iax.conf вместе с переменной ${SECRET} в вашем сопоставлении. Использование переменной ${SECRET} в сопоставлении вызывает отправку сменяющегося пароля в ответ, который затем может использоваться для аутентификации через IAX2. Вот пример определения аутентификации в файле iax.conf:

[dundi]

type=friend

context=DUNDi_Incoming

dbsecret=dundi/secret

disallow=all

allow=ulaw

allow=alaw

Пароль хранится в AstDB и сменяется каждые 3600 секунд (1 час). Чтобы использовать пароль в сопоставлениях, измените сопоставления в dundi.conf на использование ${SECRET} вместо very_secret_secret. Это сопоставление, которое мы настроили в системе Ванкувер:

[mappings]

; Всё в одной строке

extensions => RegisteredDevices,0,SIP,dundi:${SECRET}@

vancouver.shifteight.org/

${NUMBER},nopartial

Контроль ответов

Ответы контролируются с помощью диалплана. Всякий раз, когда входящий запрос совпадает с дилпланом, настроенным для сопоставления (независимо от того, является ли запрос определенным номером или шаблоном), будет отправлен ответ. Если запрос не совпадает с диалпаном, ответ не отправляется. В примере, который мы строим, номер 1000 является единственным, который может быть сопоставлен и, таким образом, генерировать ответ.

В следующих разделах мы рассмотрим некоторые из методов, которые можно использовать для управления ответами на запросы.

Добавление ответов вручную

Файл extensions.conf обрабатывает, какие номера вы рекламируете и что вы делаете с вызовами, которые подключаются к ним.

Простой способ управления ответами — добавить их вручную в контекст [RegisteredDevices]. Если бы у нас было несколько номеров в одном из наших местоположений, мы могли бы добавить их все в этот контекст:

[RegisteredDevices]

exten => 1000,1,NoOp()

exten => 1001,1,NoOp()

exten => 1002,1,NoOp()

Здесь используется приложение NoOp(), поскольку сопоставление и ответ выполняются только по добавочному номеру, а диалплан не выполняется. Хотя мы можем перегрузить этот контекст и заставить его также быть местом назначения для наших вызовов, но это не рекомендуется. Другие причины использования приложения NoOp() должны стать ясными по мере продвижения.

Использование шаблонов совпадений

Конечно, добавление всех, кому мы хотим ответить вручную, было бы глупо, особенно если бы мы хотели рекламировать больший набор номеров, например все номера с кодом города. Как упоминалось ранее, в нашем примере мы могли бы позволить нашим офисам в Торонто и Ванкувере звонить друг от друга при совершении бесплатных или дешевых звонков из другого места.

Мы можем ответить всем по коду города, используя совпадения шаблонов, как и в других частях диалплана:

[RegisteredDevices]

exten => _416NXXXXXX,1,NoOp()

exten => _647NXXXXXX,1,NoOp()

exten => _905NXXXXXX,1,NoOp()

Мы также можем рекламировать полный или частичный диапазон номеров, используя совпадения шаблонов:

[RegisteredDevices]

exten => _1[1-3]XX,1,NoOp() ; extensions 1100->1399

exten => _1[7-9]XX,1,NoOp() ; extensions 1700->1999

Совпадения шаблонов — хороший способ добавления диапазонов чисел, но они все еще статичны. В следующем разделе мы рассмотрим, как можно добавить некоторую текучесть в контекст RegisteredDevices.

Динамическое добавление добавочных номеров

В некоторых случаях мы можем рекламировать только номера в нашем местоположении, которые в настоящее время зарегистрированы в системе. Возможно, у нас есть продавец, который летает между офисами в Торонто и Ванкувере, подключает свой ноутбук к сети и регистрируется в любом месте, где он сейчас находится. В этом случае мы хотели бы удостовериться в том, что звонки этому человеку направляются в соответствующий офис, с тем чтобы избежать ненужной отправки звонков по всей стране.

Параметры regcontext и regexten в iax.conf и sip.conf полезны в этом деле. При регистрации пира значение, определенное regexten для зарегистрированного пира, приведет к заполнению того же значения внутри контекста, как определено атрибутом regcontext. Так, например, если мы определяем regcontext в разделе [general] sip.conf, чтобы он содержал RegisteredDevices, и определяем regexten для каждого пира, чтобы он содержал добавочный номер этого пира, когда пиры регистрируются, контекст RegisteredDevices будет заполнен автоматически для нас. Мы изменим наш sip.conf, чтобы он выглядел так:

[general]

regcontext=RegisteredDevices

[0000FFFF0001](office-phone)

regexten=1001

а теперь перезагрузим chan_sip.so.

Теперь мы зарегистрируем наше устройство в системе и посмотрим на контекст RegisteredDevices:

*CLI> dialplan show RegisteredDevices

[ Context ‘RegisteredDevices’ created by ‘SIP’ ]

‘1001’ => 1. Noop(0000FFFF0001) [SIP]

‘1002’ => 1. Noop(0000FFFF0002) [SIP]

С нашими зарегистрированными устройствами и контекстом, используемым для определения когда заполнять ответ, единственная оставшаяся задача — включить контекст LocalSets в контекст DUNDi_Incoming чтобы разрешить маршрутизацию вызовов к конечным точкам.

Использование функций диалплана в сопоставлениях

Иногда полезно использовать функцию диалплана в сопоставлениях для управления ответами пира. На протяжении всей этой книги мы расхваливали преимущества отделения добавочного номера пользователя от устройства, чтобы разрешить hot-desking. Поскольку другой конец просто собирается запросить добавочный номер и не обязательно будет знать местоположение устройства в нашей системе, мы можем использовать функции DB() и DB_EXISTS() в сопоставлении для выполнения поиска из нашей AstDB для вызова устройства.3

В предыдущих версиях Asterisk максимальная длина поля destination (см. «Создание контекстов сопоставления«) составляла 80 символов, что делало использование вложенных функций диалплана практически невозможным. В настоящее время максимальная длина — 512 знаков.

Во-первых, мы должны убедиться, что наша база данных заполнена информацией, которой мы могли бы ответить. Хотя это обычно делается диалпланом, написанным для реализации hot-desking, мы просто добавим содержимое непосредственно из консоли Asterisk для демонстрационных целей:

*CLI> database put phones 1001/device 0000FFFF0001

Updated database successfully

При заполнении базы данных нам нужно изменить наше сопоставление, чтобы использовать некоторые функции диалплана, которые будут принимать запрошенное значение, выполнять поиск в нашей базе данных для этого значения и возвращать значение. Если в базе данных нет значения, мы вернем значение None.

Наше существующее сопоставление выглядит следующим образом:

[mappings]

; Сопоставление должно быть в одну строку

extensions => RegisteredDevices,0,SIP,

dundi:[email protected]/${NUMBER},nopartial

Наш текущий пример просто отражает тот же добавочный номер, который был запрошен, вместе с некоторой информацией аутентификации. Запрашиваемое число — это номер, который ищет пир. Однако, поскольку мы используем hot-desking, добавочный номер телефона может быть в разных местах, поэтому мы можем вернуть идентификатор устройства напрямую.4 Мы можем сделать это с использованием функций диалплана в нашем ответе. Хотя у нас может не быть полной мощности диалплана (нескольких линий, сложной логики и т.д.) в нашем распоряжении мы можем, по крайней мере, использовать некоторые из более простых функций диалплана, таких как DB(), DB_EXISTS() и IF().

Мы собираемся заменить ${NUMBER} следующим битом логики диалплана:

; все это должно быть в одной строке

${IF($[${DB_EXISTS(phones/${NUMBER}/device)}]?

${DB(phones/${NUMBER}/device)}:None)}

Если мы сломаем это, то получим оператор IF(), который вернет либо истину, либо ложь. Если ложь, то возвращается значение None. Если истина, мы возвращаем значение, расположенное в базе данных в phones/${NUMBER}/device (где ${NUMBER} содержит значение 1001 для нашего примера), используя функцию DB(). Чтобы определить какое значение будет возвращать функция IF(), мы используем функцию DB_EXISTS(). Эта функция проверяет, существует ли значение в phones/${NUMBER}/device в AstDB и возвращает 1 или 0 (истина или ложь).

Функция DB_EXISTS() не только возвращает 1 или 0, но и задает переменную канала $⁠{DB_RESULT}, содержащую значение внутри базы данных, если возвращаемое значение равно 1. Однако мы не можем использовать это значение, потому что функция IF() оценивается до поля условия, что означает, что ${DB_RESULT} будет пустым. Таким образом, нам нужно использовать функцию DB() для поиска значения до оцениваемого поля условия.

После перезагрузки pbx_dundi.so из консоли (module reloadpbx_dundi.so), мы можем выполнить поиск с другого сервера и проверить результат:

vancouver*CLI> dundi lookup 1001@extensions bypass

1. 0 SIP/dundi:very_awesome_password/0000FFFF0001 (EXISTS)

from ff:ff:ff:ff:ff:ff, expires in 3600 s

DUNDi lookup completed in 77 ms

С помощью функций диалплана вы можете сделать ответы в своих диалпланах намного более динамичными. В следующем разделе мы рассмотрим, как вы можете выполнять эти поиски из диалплана с помощью функций DUNDILOOKUP(), DUNDIQUERY() и DUNDIRESULT().

При выполнении поиска с помощью примера в этой главе, поскольку все пиры в вашей сети будут возвращать результат (None или значение которое вы хотите), вам нужно будет использовать функции DUNDIQUERY() и DUNDIRESULT() для анализа списка возвращаемых результатов. Альтернативой было бы попробовать позвонить SIP/dundi:very_long_pass@remote_server/None но это будет не очень эффективно. Возможно, вы даже захотите обработать расширение None элегантно, если оно будет вызвано.

Выполнение поиска из диалплана

Выполнение поиска из диалплана — это действительно хлеб и масло всего этого, потому что это позволяет использовать более динамическую маршрутизацию изнутри диалплана. С помощью DUNDi вы можете выполнять поиск и маршрутизацию вызовов в кластере с помощью функций DUNDILOOKUP() или DUNDIQUERY() и DUNDIRESULT().

Функция DUNDILOOKUP() заменяет старое приложение диалплана DUNDiLookup(), выполняя почти те же функции. С помощью DUNDILOOKUP() вы выполняете поиск как в консоли Asterisk, и результат может быть сохранен в переменной канала или использоваться везде, где вы можете использовать функцию диалплана. Вот пример:

[TestContext]

exten => 1001,1,Verbose(2,Look up extension 1001)

same => n,Set(DUNDi_Result=${DUNDILOOKUP(1001,extensions,b)})

same => n,Verbose(2,The result of the lookup was ${DUNDi_Result})

same => n,Hangup()

Аргументы, переданные DUNDILOOKUP(): extension,context,options. Для функции DUNDILOOKUP() доступен только один параметр b, который используется для обхода локального кэша. Преимущество использования функции DUNDILOOKUP() заключается в простоте и удобстве в использовании. Недостатком является то, что она будет устанавливать только первое возвращаемое значение; если возвращается несколько значений, они будут отброшены.

Вы не всегда захотите использовать опцию обхода при выполнении поиска, потому что использование кэша — это то, что снизит количество запросов по вашей сети и ограничит количество необходимых ресурсов. Мы используем его в наших примерах просто потому, что он полезен для целей тестирования, поэтому знаем что каждый раз возвращаем результат, а не просто кэшированное значение из предыдущего поиска.

Чтобы проанализировать несколько возвращаемых значений, нам нужно использовать функции DUNDIQUERY() и DUNDIRESULT(). Каждый из них играет важную роль в просеивании нескольких возвращаемых значений из поиска. Функция DUNDIQUERY() выполняет начальный поиск и сохраняет полученный хэш в память. Затем возвращается значение ID, которое можно сохранить в переменной канала. Затем значение ID, возвращаемое функцией DUNDIQUERY(), может быть передано функции DUNDIRESULT() для анализа возвращаемых значений из запроса.

Давайте посмотрим на некоторый диалплан, который использует эти функции:

[TestContext]

exten => _1XXX,1,Verbose(2,Looking up results for extension ${EXTEN})

; Выполнить поиск и сохранить полученный ID в DUNDI_ID

same => n,Set(DUNDI_ID=${DUNDIQUERY(${EXTEN},extensions,b)})

same => n,Verbose(2,Showing all results returned from the DUNDi Query)

; Функция DUNDIRESULT() может возвращать количество результатов с помощью ‘getnum’

same => n,Set(NumberOfResults=${DUNDIRESULT(${DUNDI_ID},getnum)})

same => n,Set(ResultCounter=1)

; Если результат меньше 1, результаты не возвращаются

same => n,GotoIf($[0${NumberOfResults} < 1]?NoResults,1)

; Начало цикла, показывающего возвращаемые значения

same => n,While($[0${ResultCounter} <= ${NumberOfResults}])

; Сохраните возвращаемый результат в позиции ${ResultCounter} в thisResult

same => n,Set(thisResult=${DUNDIRESULT(${DUNDI_ID},${ResultCounter})})

; Показать текущий результат в консоли

same => n,Verbose(2,One of the results returned was: ${thisResult})

; Увеличить счетчик на единицу

same => n,Set(ResultCounter=${INC(ResultCounter)})

; Конец нашего цикла

same => n,EndWhile()

same => n,Playback(silence/1)

same => n,Playback(vm-goodbye)

same => n,Hangup()

; Если результат не найден — выполнить этот диалплан

exten => NoResults,1,Verbose(2,No results were found)

same => n,Playback(silence/1)

same => n,Playback(invalid)

same => n,Hangup()

В нашем примере диалплан выполняет поиск с помощью функции DUNDIQUERY() и сохраняет результирующее значение ID в переменной канала DUNDI_ID. Используя функцию DUNDIRESULT() и параметр getnum мы храним общее количество возвращаемых результатов в переменной канала NumberOfResults. Затем мы устанавливаем переменную канала ResultCounter в 1 в качестве нашей начальной позиции в цикле.

Используя GotoIf() проверяем, меньше ли возвращаемого ${NumberOfResults} и, если да, переходим к расширению NoResults, где мы делаем Playback() «Invalid Extension». Если хотя бы один номер расширение будет найден, мы продолжим в диалплане.

Используя приложение While(), мы проверяем, меньше или равно ${ResultCounter} значения $⁠{NumberOfResults}. Если это правда — мы продолжим в диалплане, а в противном случае перейдем к приложению EndWhile().

Для каждой итерации нашего цикла функция DUNDIRESULT() используется для сохранения значения в позиции ${ResultCounter} в переменную канала thisResult. После сохранения значения мы выводим его в консоль Asterisk с помощью приложения Verbose(). После этого увеличиваем значение ResultCounter на единицу, используя функцию INC(). Затем наш цикл теста выполняется снова в цикле While() и будет продолжаться, пока значение ${ResultCounter} меньше или равно значения ${NumberOfResults}.

Используя тот же тип логики, мы могли бы проверить значения, отличные от None и, если такое значение найдено, выполнить ExitWhile() и продолжить в диалплане для выполнения вызова конечной точки. Логика диалплана может выглядеть примерно так:

[subLookupExtension]

exten => _1XXX,1,Verbose(2,Looking up results for extension ${EXTEN})

; Выполнить поиск и сохранить полученный ID в DUNDI_ID

same => n,Set(DUNDI_ID=${DUNDIQUERY(${EXTEN},extensions,b)})

same => n,Set(NumberOfResults=${DUNDIRESULT(${DUNDI_ID},getnum)})

same => n,Set(ResultCounter=1)

; Если результат не найден, вернуть ‘None’

same => n,GotoIf($[0${NumberOfResults} < 1]?NoResults,1)

; Выполнить нашу петлю

same => n,While($[0${ResultCounter} <= ${NumberOfResults}])

; Получить текущее значение

same => n,Set(thisResult=${DUNDIRESULT(${DUNDI_ID},${ResultCounter})})

; Если текущее возвращаемое значение не равно None, у нас есть

; результирующее местоположение для вызова, и мы можем выйти из цикла

same => n,ExecIf($[«${thisResult}» != «None»]?ExitWhile())

; Если мы зашли так далеко, но еще не возвращено значение, которое мы хотим

; использовать, увеличте счетчик и попробуйте следующее значение.

same => n,Set(ResultCounter=${INC(ResultCounter)})

; Конец нашей петли

same => n,EndWhile()

; Мы находимся здесь, потому что дошли до конца цикла или нашли

; значение, которое хотим вернуть. Проверьте, что это оно. Если у нас просто

; закончились значения, вернуть «None».

;

same => n,GotoIf($[«${thisResult}» = «None»]?NoResults,1)

; Если мы находимся здесь, у нас есть значение, которое хотим вернуть.

same => n,Return(${thisResult})

; Если не было приемлемых результатов, вернуть значение «None»

exten => NoResults,1,Verbose(2,No results were found)

same => n,Return(None)

С функциями DUNDIQUERY() и DUNDIRESULT() у вас есть много возможностей контролировать как обрабатывать возвращаемые результаты и выполнять логику маршрутизации с этими значениями.

Вывод

В этой главе мы рассмотрели, как протокол DUNDi помогает выполнять поиск других систем Asterisk в кластере, выполнять динамическую маршрутизацию. С помощью DUNDi вы можете взять несколько систем и контролировать когда и где вызовы совершаются в них, предоставляя возможности обхода дороговизны оплаты вызовов и даже предоставляя своим сотрудникам возможность перемещаться между физическими местоположениями, а также ограничивать количество внесистемных переходов, которые должен совершить вызов, чтобы найти их.

В то время как первоначальное намерение DUNDi состояло в том, чтобы помочь нам мигрировать из централизованных служб каталогов — намерение, которое еще не принесло результатов — DUNDi является чрезвычайно эффективным и полезным инструментом, который можно использовать в организациях для рекламы и маршрутизации вызовов динамически между системами в облачной среде. DUNDi — это инструмент, который дает большую власть администраторам Asterisk, желающим создать распределенную сеть.

1Для публичных сетей и сетей, не контролируемых вашей организацией, используйте ISN (абонентский номер ITAD) это наш предпочтительный метод. Мы говорили об ITAD и ISN в “ISN, ITAD, и freenum.org” в Главе 12.

2Файлы dundi.conf и extensions.conf должны быть настроены. Мы решили настроить sip.conf для целей адресной рекламы в нашей сети, но DUNDi является протоколом-агностиком, поэтому вместо этого можно использовать iax.conf, h323.conf или mgcp.conf. DUNDi просто выполняет поиск; стандартные методы совершения вызовов по-прежнему необходимы.

3…или func_odbc, или func_curl, или res_ldap (используя функцию REALTIME_FIELD()).

4Мы также рассмотрели использование функций GROUP() и GROUP_COUNT() для поиска текущего использования канала в удаленной системе, чтобы определить, в какое место маршрутизировать вызовы (с самой низкой занятостью канала) в качестве простого балансировщика нагрузки.

Остались вопросы?

Я - Кондрашин Игорь, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.

VoIP оборудование


ближайшие курсы

10 доводов в пользу Asterisk

Распространяется бесплатно.

Asterisk – программное обеспечение с открытым исходным кодом, распространяется по лицензии GPL. Следовательно, установив один раз Asterisk вам не придется дополнительно платить за новых абонентов, подключение новых транков, расширение функционала и прочие лицензии. Это приближает стоимость владения станцией к нулю.

Безопасен в использовании.

Любое программное обеспечение может стать объектом интереса злоумышленников, в том числе телефонная станция. Однако, сам Asterisk, а также операционная система, на которой он работает, дают множество инструментов защиты от любых атак. При грамотной настройке безопасности у злоумышленников нет никаких шансов попасть на станцию.

Надежен в эксплуатации.

Время работы серверов некоторых наших клиентов исчисляется годами. Это значит, что Asterisk работает несколько лет, ему не требуются никакие перезагрузки или принудительные отключения. А еще это говорит о том, что в районе отличная ситуация с электроэнергией, но это уже не заслуга Asterisk.

Гибкий в настройке.

Зачастую возможности Asterisk ограничивает только фантазия пользователя. Ни один конструктор шаблонов не сравнится с Asterisk по гибкости настройки. Это позволяет решать с помощью Asterisk любые бизнес задачи, даже те, в которых выбор в его пользу не кажется изначально очевидным.

Имеет огромный функционал.

Во многом именно Asterisk показал какой должна быть современная телефонная станция. За многие годы развития функциональность Asterisk расширилась, а все основные возможности по-прежнему доступны бесплатно сразу после установки.

Интегрируется с любыми системами.

То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.

Позволяет телефонизировать офис за считанные часы.

В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.

Отличная масштабируемость.

Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.

Повышает управляемость бизнеса.

Asterisk дает не просто набор полезных функций, он повышает управляемость организации, качества и комфортности управления, а также увеличивает прозрачность бизнеса для руководства. Достичь этого можно, например, за счет автоматизации отчетов, подключения бота в Telegram, санкционированного доступа к станции из любой точки мира.

Снижает расходы на связь.

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