/dracon

Draft of the config. Virtual TFTP-server to building configs on the fly.

Primary LanguagePythonGNU General Public License v2.0GPL-2.0

Dracon (Draft of the config) - TFTP-сервер для Linux/FreeBSD, предназначенный для генерации конфигурационных файлов по требованию. Файлы генерируются на основе простых текстовых шаблонов и выгружаются по TFTP-протоколу. Уникальные файлы, передаваемые сервером или отправляемые на сервер, сохраняются в базе MySQL.

Предназначение сервиса

Сервис Dracon изначально разрабатывался для генерации 'на лету' конфигурационных файлов для коммутаторов D-Link, таких как DES-3028, DES-3200 и т.д. При конфигурировании этих устройств очень часто нужно определять команды с учетом ролей портов. Так, например, при настройке функционала обнаружения петель, сегментации трафика и управления мультикаст-трафиком требуется заранее определить как будет использоваться тот или иной порт. Очевидно, что обнаружение петель имеет смысл включать только на тех портах, куда будет подключено клиентское оборудование, а при конфигурировании multicast-vlan в качестве источника трафика требуется явно задать uplink-порт.

Роли портов можно определять заранее. Например, для всех коммутаторов DES-3200-28 можно использовать порты 1-24 как абонентские, 25-28 как магистральные, а сам 25-й порт при этом считать аплинком. Но чем больше разрастается сеть, тем больше появляется вынужденных отклонений от схемы - иногда приходится использовать другой порт в качестве аплинка, подключать собственное оборудование в абонентские порты или же самих абонентов в магистральные. При этом конфигурация, которая была разработана для "стандартного" коммутатора, приведет к неправильной работе устройства. Загружая на устройство такую конфигурацию нужно быть готовым к непредвиденным последствиям. © :)

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

Возможности Dracon

  • Генерация конфигурационного файла с учетом типов портов и пользовательских параметров (по одному для порта и одному параметру для самого устройства)
  • Сохранение в базе MySQL файлов, передаваемых в обоих направлениях (кроме файлов ПО)
  • Возможность выгрузки программного обеспечения (прошивки) вместо файла конфигурации
  • Применение пользовательских функций для обработки пользовательских параметров
  • Возможность выгрузки по запросу конкретной секции конфигурационного файла
  • Работа одновременно с несколькими устройствами (TFTP-клиентами)

Особенности работы

  • Работа системным сервисом (daemon) под FreeBSD/Linux
  • Гибкая система конфигурирования, возможность определять типы портов, устройств и доступных команд
  • Отсутствие привязки к конкретному оборудованию или вендору

Требования

  • Операционная система Linux или FreeBSD
  • Python с модулем MySQLdb
  • Доступ к вашему MySQL-серверу (базе биллинга) для получения списка устройств и портов
  • Доступ к MySQL для сохранения файлов (опционально)

Принцип работы

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

ip type custom
10.90.90.95 24 192.168.0.0/24

Здесь type - это идентификатор типа устройства. В дальнейшем для удобства происходит сопоставление типов текстовым именам устройств. Параметр custom - произвольные пользовательские данные, к примеру адрес установки коммутатора.

Затем запрашивается информация о портах. Запрос должен вернуть таблицу вида:

ip port ptype comment
10.90.90.95 1 1 user12345

В этом запросе ptype определяет тип (роль) порта - абонентский, магистральный, неисправный и т.д. Параметр comment - произвольный комментарий порта, содержащий, к примеру, логин пользователя из биллинга.

После этого Dracon начинает прослушивать порт 69 (по умолчанию) для работы в качестве TFTP-сервера. При получении запроса от устройства (или обычного TFTP-клиента) сервис либо сгенерирует и отдаст клиенту конфигурационный файл, если это был запрос на чтение, либо примет от клиента файл, если это был запрос на запись. Выгруженные и загруженные файлы помещаются в базу данных MySQL.

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

Конфигурирование

Описание параметров в файле dconfig.py

Общие настройки программы

Параметр Описание
interface_ip IP-адрес интерфейса, на котором будет работать демон.
port UDP-порт для TFTP-сервера.
cycle_int Время в секундах, через которое надо обновлять список портов и устройств.
sleep_def Пауза по умолчанию при опросе UDP-сокета.
sleep_int Время простоя (данные не поступают) в секундах, через которое надо выставить паузу по умолчанию.
log_file Имя файла журнала.
log_size Размер файла журнала при достижении которого начинается ротация.
log_backupcount Количество архивных копий журнала.

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

Настройки для MySQL-сервера, откуда будут забираться данные

Параметр Описание
mysql_addr Адрес MySQL-сервера биллинга.
mysql_user Имя пользователя.
mysql_pass Пароль.
mysql_base Имя базы данных.

Настройки для PostgreSQL-сервера, откуда будут забираться данные. Используется как альтернатива MySQL

Параметр Описание
postgresql_addr Адрес PostgreSQL-сервера биллинга.
postgresql_user Имя пользователя.
postgresql_pass Пароль.
postgresql_base Имя базы данных.
use_postgresql Параметр, определяющий, использовать ли MySQL либо же PostgreSQL (когда установлен в True).

Запросы к MySQL/PostgreSQL

Параметр Описание
devices_query Запрос к базе данных для получения списка устройств.
ports_query Запрос к базе данных для получения сведений о портах коммутатора.

Настройки для MySQL-сервера, где будет храниться конфигурация и информация о транзакциях

Параметр Описание
mysql_addr_w Адрес MySQL-сервера для сохранения конфигурационных файлов.
mysql_user_w Имя пользователя.
mysql_pass_w Пароль.
mysql_base_w Имя базы данных.
mysql_ctbl_w Имя таблицы для сохранения конфиругационных файлов.
mysql_ttbl_w Имя таблицы для сохранения транзакций.

Распознавание устройств, портов и их типов

Параметр Описание
dev_types Соответствие идентификаторов типов устройств их названиям.

Параметр dev_types задается в виде словаря python. Пример:

dev_types = {
     24 : 'DES-3200-28',        # DES-3200-28/A1
    218 : 'DES-3200-28',        # DES-3200-28/B1
    210 : 'DES-3200-28_C1',     # DES-3200-28/C1
    216 : 'DES-3200-18',        # DES-3200-18/A1
    217 : 'DES-3200-18',        # DES-3200-18/B1
    209 : 'DES-3200-18_C1',     # DES-3200-18/C1
    205 : 'DES-3028',           # DES-3028
    215 : 'DGS-3000-24TC',      # DES-3000-24TC
    252 : 'DGS-3000-26TC',      # DES-3000-24TC
}
Параметр Описание
ports_types Словарь с кодами портов и их сокращенными обозначениями

Параметр ports_types содержит соответствие цифровых и символьных кодов портов. Цифровые коды хранятся в биллинге, а символьные ипользуются в конфигурационных шаблонах. Для себя я определил 9 типов портов:

Код Тип
1 абонентский порт
2 магистральный
3 сломанный
4 VIP-клиент
5 вход
6 нестандартный
7 оборудование
8 вход (патчкорд)
9 магистраль (патчкорд)

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

Параметр ports_types задается в виде словаря python. Пример:

ports_types = {1:'ss', 2:'mg', 3:'br', 4:'vp', 5:'up', 6:'ns', 7:'eq', 8:'pu', 9:'pd'}
Параметр Описание
mags_list Список кодов магистральных портов.

Параметр mags_list определяет произвольный набор портов как магистральные и задается в виде списка python. Пример:

mags_list = [2,5,8,9]

Пути к каталогам и имена файлов

Параметр Описание
cf_path Путь к каталогу с файлами конфигураций.
fw_path Путь к каталогу с программным обеспечением.

Имя файла конфигураций должно соответствовать названию устройства (см. dev_types). Например, для устройства с ID=210 по пути /usr/local/etc/dracon/config/ (значение cf_path) будет производиться поиск файла DES-3200-28_C1.

Параметр Описание
fw_names Соответствие названий устройств именам файлов с программным обеспечением

Параметр fw_names содержит имя файла "прошивки" для конкретного коммутатора и задается в виде словаря python. Пример:

fw_names = {
    'DES-3200-28'    : 'DES-3200R_1.85.B008.had',
    'DES-3200-28_C1' : 'DES3200R_4.39.B008.had',
    'DES-3200-18'    : 'DES-3200R_1.85.B008.had',
    'DES-3200-18_C1' : 'DES3200R_4.39.B008.had',
    'DES-3028'       : 'DES_3028_52_V2.94-B07.had',
    'DGS-3000-24TC'  : 'DGS3000_Run_1_14_B008.had',
    'DGS-3000-26TC'  : 'DGS3000_Run_1_14_B008.had',
    }

Команды сервиса

Параметр Описание
commands Доступные команды (имена запрашиваемых файлов) и соответствующие им шаблоны с наборами команд

Параметр commands задается в виде словаря python, где значениями являются списки. Сами команды и соответствующие им шаблоны целиком определяются пользователем. Пример:

commands = {
    'acl':['*header*', '*acl*'],
    'cpu_acl':['*header*', '*cpu_acl*'],
    'accounts':['*header*', '*accounts*'],
    'stp_lbd':['*header*', '*stp_lbd*'],
    'snmp':['*header*', '*snmp*'],
    'sntp':['*header*', '*sntp*'],
    'lldp':['*header*', '*lldp*'],
    'filtering':['*header*', '*filtering*'],
    'trusted_hosts':['*header*', '*trusted_hosts*'],
    'ipm':['*header*', '*ipm*'],
    'dhcp_relay':['*header*', '*dhcp_relay*'],
    'igmp_snooping':['*header*', '*igmp_snooping*'],
    'igmp_auth':['*header*', '*igmp_auth*'],
    'aaa':['*header*', '*aaa*'],
    'multi_filter':['*header*', '*multi_filter*'],
    'cos':['*header*', '*cos*'],
    'mon_log':['*header*', '*mon_log*'],
    'pdesc':['*header*', '*p_desc*'],
    'config':['*header*', '*acl*', '*cpu_acl*', '*accounts*', '*stp_lbd*', '*snmp*', '*sntp*', '*lldp*', '*filtering*', '*trusted_hosts*',
    '*ipm*', '*dhcp_relay*', '*igmp_snooping*', '*igmp_auth*', '*aaa*', '*multi_filter*', '*cos*', '*mon_log*', '*p_desc*', '*bottom*']
    }

Данная конструкция обозначает следующее: если запросить у сервиса Dracon файл с именем acl, то программа заглянет в commands, увидит, что этому имени соответствуют секции конфигурационного файла с именами '*header*' и '*acl*', найдет эти секции в шаблоне файла, обработает и выдаст их содержимое. Подробнее о секциях будет рассказано ниже.

Нетрудно догадаться, что файл с именем config содержит все доступные команды.

Параметр Описание
helpinfo Содержимое справки, получаемой по команде (имени файла) 'help'

Параметр helpinfo представляет собой строку, в которой можно написать мини-справку, возвращаемую при выполнении команды help

Примечание: С точки зрения TFTP-клиента команда является именем файла. Чтобы выполнить команду config, нужно затребовать с TFTP-сервера файл config.

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

tftp -i dracon.myhost get 10.90.90.100@config

выгрузит полную конфигурацию для устройства с IP-адресом 10.90.90.100.

Помимо настраиваемых команд и команды help имеются еще две зарезервированные:

Команда Описание
firmware Выгрузка программного обеспечения для заданного устройства (имя файла задается в файле конфигурации)
backup Выгрузка из базы загруженных файлов последней конфигурации для заданного устройства

Описание параметров в файле dfunc.py

Dracon позволяет использовать пользовательские функции для обработки параметров comment и custom. Эти функции хранятся в файле dfunc.py и написаны на обычном python. Примеры функций:

Пользовательская функция: Получение hex-значения 2-го октета IP-адреса

def fn_2oct(src):
    try:
        return hex(int(src.split('.')[1]))[2:].zfill(2)
    except:
        return ""

Пользовательская функция: Получение hex-значения 3-го октета IP-адреса

def fn_3oct(src):
    try:
        return hex(int(src.split('.')[2]))[2:].zfill(2)
    except:
        return ""

Пользовательская функция: Получение hex-значения номера порта

def fn_xp(n):
    try:
        return hex(int(n))[2:].zfill(2)
    except:
        return ""

Пользовательская функция: Получение транслитерированного значения поля custom до разделителя '|'

def fn_tr_cst1(src):
    return Translit(src.split('|')[0])

О практическом применении пользовательских функций рассказано в следующем разделе.

Написание шаблонов конфигурации

Ну вот мы и подошли к самому интересному - к написанию шаблонов конфигурационных файлов. Предположим, мы хотим создать шаблон файла конфигурации для устройства DES-3200-28/C1. Согласно параметрам cf_path и dev_types файл шаблона должен находиться по адресу /usr/local/etc/dracon/config/DES-3200-28_C1.

Структура файла шаблона:

:::*Заголовок секции*:::
Тело
...
секции
<пустая строка>

:::*Заголовок другой секции*:::
Тело другой секции
<пустая строка>

Пример секции, содержащей "шапку" конфигурационного файла коммутатора:

:::*header*:::
#-------------------------------------------------------------------------------
#                       DES-3200-28 Fast Ethernet Switch
#                                Configuration
#
#                          Firmware: Build 4.39.B008
#           Copyright(C) 2012 D-Link Corporation. All rights reserved.
#

Внутри тела секции мы можем использовать специальные конструкции, которые при выгрузке будут заменены на некоторые значения. Специальная конструкция [mags] будет заменена на диапазон портов коммутатора с типами, которые были описаны в переменной mags_list. А конструкция [all] будет заменена на диапазон всех портов коммутатора.

В параметре ports_types мы определяли буквенные обозначения диапазонов портов, которые можно использовать в шаблонах конфигурации. Так, конструкция [ss] будет заменена на диапазон всех портов с кодом 1, а конструкция [eq] - на диапазон всех портов с кодом 7.

Можно получить конкретный порт из диапазона. Конструкция [ss#5] будет заменена на 5 в случае, если порт №5 входит в диапазон портов [ss].

Пользовательская функция вызывается конструкцией вида {fn_2oct#custom}. В данном примере будет вызвана пользовательская функция fn_2oct, а в качестве параметра ей будет передано значение custom.

Можно получить комментарий для конкретного порта используя конструкцию вида {comment#7}. В этом случае при замене будет произведена замена шаблона на значение comment порта 7. Кстати, такую конструкцию можно применять и как аргумент для пользовательской функции, например: {fn_tr#{comment#28}}. В этом случае будет получен комментарий для порта №28, который потом будет передан в качестве параметра внешней функции fn_tr. В данном примере эта функция выполняет транслитерацию текста.

Внимание! Если условие для замены не выполнилось, строка будет закомментирована символом #, а заменяемая конструкция останется неизменной.

Пример: У нас есть порт 25 с кодом 5 (up) и нет портов с кодом 8 (pu). В этом случае строки

config igmp_snooping multicast_vlan mvr add source_port [up]
config igmp_snooping multicast_vlan mvr add source_port [pu]

будут возвращены как

config igmp_snooping multicast_vlan mvr add source_port 25
#config igmp_snooping multicast_vlan mvr add source_port [pu]

Вторая команда не будет выполнена коммутатором, т.к. начинается с символа #.

Для использования доступны и две переменные окружения:

Переменная Описание
{$target} IP-адрес устройства, для которого собирается конфигурация.
{$datetime} Текущие дата и время.

Примеры использования заменяемых конструкций

Подстановка диапазона абонентских и магистральных портов

config cpu_filter l3_control_pkt   [ss] all state enable
config cpu_filter l3_control_pkt [mags] all state disable

Подстановка значения comment для портов 1 и 2

config ports  1 description "{comment#1}"
config ports  2 description "{comment#2}"

Вызов пользовательских функций fn_2oct и fn_3oct с параметром custom и подстановка номеров портов 1 и 2 из диапазона абонентских портов.

config access_profile profile_name pcf add access_id 101 packet_content offset_chunk_1 0x0800 offset_chunk_2 0x0A{fn_2oct#custom}{fn_3oct#custom}08 port  [ss#1] permit
config access_profile profile_name pcf add access_id 102 packet_content offset_chunk_1 0x0800 offset_chunk_2 0x0A{fn_2oct#custom}{fn_3oct#custom}10 port  [ss#2] permit

Установка под Linux (пример для Centos 7)

  • Выполните команду: git clone https://github.com/xcme/dracon.git
  • Скопируйте файл 'dracon.service' из директории './linux/centos/' в '/etc/systemd/system/'.
  • Запустите сервис командой systemctl start dracon.
  • Добавьте автозапуск сервиса при загрузке системы командой systemctl enable dracon.

Установка под FreeBSD

  • Скопируйте файл dracon из директории 'freebsd' в /usr/local/etc/rc.d/, а остальные файлы в /usr/local/etc/dracon/.
  • Добавьте строку dracon_enable="YES" в файл /etc/rc.conf.
  • Запустите сервис командой service dracon start.

Список изменений

[4.1.31] - 2018.01.31

Добавлено

  • Поддержка PostgreSQL в качестве источника данных
  • Отдельная таблица для хранения конфигурационных файлов
  • Ротация логов

Изменено

  • Вместо MongoDB теперь используется MySQL
  • Теперь используется общая таблица для транзакций, а само направление транзакции указано в дополнительном поле
  • Записи о транзакциях теперь попадают в базу данных во всех случаях, а не только при уникальной передаваемой конфигурации
  • Таблица для хранения транзакций больше не содержит самого конфигурационного файла, который теперь хранится в отдельной таблице
  • Максимальный размер загружаемого на сервер файла уменьшен до 64 КБ
  • Для каждого вывода в лог теперь определена его важность (severity)
  • Изменены комментарии в файле конфигурации и имена некоторых переменных

Исправлено

  • Падение программы при недоступном на момент запуска сервере баз данных
  • Косметические улучшения кода
  • Исправлены некоторые опечатки в тексте

Удалено

  • Поддержка MongoDB полностью удалена

[2.6.24] - 2016.06.24

Добавлено

  • Добавлена возможность использовать комментарий к порту как аргумент пользовательской функции
  • Добавлена переменная окружения {$datetime} для получения текущей даты и времени

Изменено

  • Пользовательские функции теперь нужно обрамлять в фигурные скобки {}, а не в круглые ()
  • Функция транслитерации теперь считается пользовательской и хранится в dfunc.py

Удалено

  • Удален набор портов по умолчанию

[1.0.0] - 2015.07.11

Добавлено

  • Выгрузка последней конфигурации устройства при запросе файла backup
  • Переменная окружения {$target} для получения IP-адрес устройства

Изменено

  • Новые возможности добавлены в описание

Исправлено

  • Подсчет MD5-суммы при получении файла