Сформулируем задачу, которую надо решить: необходимо сформировать доменные имена для маршрутизаторов и их интерфейсов. Скрипт должен из подобной конфигурации (в данном случае cisco):
hostname R1.greenhouseсформировать две записи, для прямой зоны и для обратной зоны:
domain service.prov.ru
interface FastEthernet0/1.24
description Line to Red House
ip address 10.24.1.1 255.255.255.254
Fa0/1.24.Line_to_Red_House.R1.greenhouse.service.prov.ru. IN A 10.24.1.1
1.1.24.10.in-addr.arpa. IN PTR Fa0/1.24.Line_to_Red_House.R1.greenhouse.service.prov.ru.
Есть плохая новость: это неправильные доменные имена. По правилам вы можете использовать только цифры, буквы и “-”. Причём дефис не может стоят первым и последним. Для соответствия правилам запись должна выглядеть примерно так:
Fa0-1-24.Line-to-Red-House.R1.greenhouse.service.prov.ru. IN A 10.24.1.1С читабельностью есть явные проблемы. А если имя интерфейса будет начинаться не цифры, отличной от нуля, да ещё с двоеточием? После приведения такого к стандарту, понять, что и где в этих цифрах и буквах становится довольно сложно. А хотелось бы, увидев вывод traceroute краем глаза, сразу понимать, что, где и как, а не заниматься интерпретацией вывода методом гадания. Поэтому bind был настроен с опцией, которая позволяет работать с нестандартными именами. А скрипт, соответственно, делает максимально читабельные имена. Стыковка DNS сетевиков и глобального DNS это немного отдельный разговор, но проще всего выделить для сетевого оборудования отдельный домен, например, network.megaproduct.org.
Для формирования файлов зон нам необходимо собрать значения hostname маршрутизаторов, имена интерфейсов, значение description на этих интерфейсов и ip-адреса этих интерфейсов. Единственным исключением будет запись, содержащая ip-адрес, принадлежащий интерфейсу Loopback0. Эта запись не будет содержать имя интерфейса и значение его description, так как это доменное имя маршрутизатора.
Как найти нужный OID?
Давайте определимся с OIDами для получения имён интерфейсов, ip адресов и description с этих интерфейсов. Наша цель универсальность, поэтому будем использовать OID стандартных MIB файлов, которые реализованы у всех вендоров. Как правило, самый простой способ найти нужный OID, это не чтение документации или гугление, а использование snmpwalk или любого MIB браузера, который вам больше понравится. Смотрим, что может выдать конкретное оборудование, выбираем нужное. Запустив snmpwalk, вы получите все OID, поддерживаемые устройством со их значениями на момент опроса.
Для того, чтобы начать работу с snmpwalk необходимо поставить пакет snmp и snmp-mibs-downloader:
sudo apt-get install snmpЕсли необходимо, чтобы утилиты работали с именованными OID в конфигурационном файле /etc/snmp/snmp.conf добавляем используемые mib или ключевое слово ALL.
sudo apt-get install snmp-mibs-downloader
Конечно, очевидно, что использовать ALL не рекомендуется, количество файлов огромно, поиск по всем файлам подряд во многих случаях явно не очень хорошая идея. Однако, в случае написания программы и поиска нужного MIB файла это допустимо. Не забудьте только потом оставить только нужное и необходимое.
$ cat /etc/snmp/snmp.confПроверяем, увидел snmp наши MIB:
# As the snmp packages come without MIB files due to license reasons, loading
# of MIBs is disabled by default. If you added the MIBs you can reenable
# loading them by commenting out the following line.
mibs :SNMPv2-MIB:RFC1213-MIB:IF-MIB
#mibs :ALL
zw@note:~$
host$ snmptranslate 1.3.6.1.2.1.31.1.1.1.18Делаем запрос к реальной коробке:
IF-MIB::ifAlias
host$ snmptranslate -On IF-MIB::ifAlias
.1.3.6.1.2.1.31.1.1.1.18
host$ snmpget -v 2c -c tilitili 10.239.192.2 1.3.6.1.2.1.31.1.1.1.18.1Последняя цифра (1), которую я добавил — это ifindex. Ifndex это номер интерфейса, назначенный snmp агентом операционной системы оборудования.
IF-MIB::ifAlias.1 = STRING: Line_to_OUT
Нумерация производится при инициализации агента (при загрузке ОС). После каждой перезагрузке оборудования она будет другой, если вы не укажет маршрутизатору запоминать назначенные ifindex (гуглить по ключевым словам ifindex persist).
В работе с интерфейсами всё логично, берём OID, добавляем ifindex, делаем запрос – получаем description интерфейса с этим Ifindex.
Запросим snmpwalk, который запросит с роутера все доступные OIDы и их текущие значения:
host$ snmpwalk -v 2c -c tilitili 10.239.192.2В выводе snmpwalk есть что почитать, он очень многословен, поэтому его лучше фильтровать.
RFC1213-MIB::ifDescr.1 = STRING: «FastEthernet0/0»
RFC1213-MIB::ifDescr.2 = STRING: «FastEthernet0/1»
RFC1213-MIB::ifDescr.3 = STRING: «Null0»
RFC1213-MIB::ifDescr.36 = STRING: «Loopback0»
RFC1213-MIB::ipAdEntAddr.10.239.192.2 = IpAddress: 10.239.192.2
RFC1213-MIB::ipAdEntAddr.10.239.192.90 = IpAddress: 10.239.192.90
RFC1213-MIB::ipAdEntAddr.11.0.0.1 = IpAddress: 11.0.0.1
IF-MIB::ifName.1 = STRING: Fa0/0
IF-MIB::ifName.2 = STRING: Fa0/1
IF-MIB::ifName.3 = STRING: Nu0
IF-MIB::ifName.36 = STRING: Lo0
IF-MIB::ifAlias.1 = STRING: Line_to_OUT
IF-MIB::ifAlias.2 = STRING: Line_to_poligon
IF-MIB::ifAlias.3 = STRING:
IF-MIB::ifAlias.36 = STRING:
Можно обратить внимание, интерфейсы Fastethernet идут с 1 ifindex, а Loopback c 36. Любопытная деталь: ifDesc – выдаёт полные названия интерфейсов, ifAlias – отдаёт description интерфейсов, а ifName – выдаёт сокращенные названия интерфейсов. Использование полных названий интерфейсов было отброшено сразу, имена из-за содержимого description и так будут длинные, а полные имена интерфейсов сделают их ещё длиннее.
Если нам нужны OID, значения которых мы знаем, их проще искать из вывода snmpwalk или MIB браузер, используя поиск или фильтрацию. Если нам нужен OID, например, текущего значения утилизации CPU, то это проще искать в google или в документации:)
Поищем нужные OID по значениям ip адресов, которые нам известны:
host$ snmpwalk -v 2c -c tilitili 10.239.192.2 | grep 10.239.192.2И из вывода сразу видно, что нет OID, добавив к которому ifindex, мы бы получали ip адреса интерфейса с этим ifindex. Обратите внимание:
RFC1213-MIB::ipAdEntAddr.10.239.192.2 = IpAddress: 10.239.192.2
RFC1213-MIB::ipAdEntIfIndex.10.239.192.2 = INTEGER: 36
RFC1213-MIB::ipAdEntNetMask.10.239.192.2 = IpAddress: 255.255.255.255
RFC1213-MIB::ipAdEntBcastAddr.10.239.192.2 = INTEGER: 1
RFC1213-MIB::ipAdEntReasmMaxSize.10.239.192.2 = INTEGER: 18024
RFC1213-MIB::ipRouteDest.10.239.192.2 = IpAddress: 10.239.192.2
RFC1213-MIB::ipRouteIfIndex.10.239.192.2 = INTEGER: 36
и так далее
RFC1213-MIB::ipAdEntAddr. + ip адрес – возвращает ip адрес.
RFC1213-MIB::ipAdEntIfIndex. + ip адрес – возвращает ifindex
Таким образом, нам сначала нужно собрать ip адреса и ifndex интерфейсов с этими ip адресами, а потом по ifndex собрать имена и description интерфейсов.
Переходим к сбору данных:SNMPGETNEXT и Python
Ранее использовавшийся для сбора hostname метод snmpget для решения этой задачи совершенно не подойдёт. Этому методу нужен точный OID, а у нас такого нет. В таких ситуациях используется другой метод — snmpgetnext. В утилитах snmp за этот метод отвечает утилита snmpgetnext.
Работает это так: SNMPGETNEXT берёт OID и возвращает следующий OID и его значение.
host$ snmpgetnext -v 2c -c tilitili 10.239.192.2 1.3.6.1.2.1.4.20.1.1И так далее. Обратите внимание, мы не знаем адреса маршрутизатора при первом запросе, поэтому первое обращение идёт просто на значение OID 1.3.6.1.2.1.4.20.1.1
RFC1213-MIB::ipAdEntAddr.10.239.192.2 = IpAddress: 10.239.192.2
host$ snmpgetnext -v 2c -c tilitili 10.239.192.2 1.3.6.1.2.1.4.20.1.1.10.239.192.2
RFC1213-MIB::ipAdEntAddr.10.239.192.90 = IpAddress: 10.239.192.90
Попробуем автоматизировать эти запросы. Напишем функцию и вызовем её несколько раз:
def snmp_getnextcmd(community, ip, port, OID):
# type class 'generator' errorIndication, errorStatus, errorIndex, result[3]
# метод next для получения значений по порядку, одного за другим с помощью next()
return (nextCmd(SnmpEngine(),
CommunityData(community),
UdpTransportTarget((ip, port)),
ContextData(),
ObjectType(ObjectIdentity(OID))))
g = (snmp_getnextcmd(community_string, ip_address_host, port_snmp, OID_ipAdEntAddr))
print(g)
errorIndication, errorStatus, errorIndex, varBinds = next(g)
for name,val in varBinds:
print(name.prettyPrint(),' ====== ',val.prettyPrint())
errorIndication, errorStatus, errorIndex, varBinds = next(g)
for name,val in varBinds:
print(name.prettyPrint(),' ====== ',val.prettyPrint())
errorIndication, errorStatus, errorIndex, varBinds = next(g)
for name,val in varBinds:
print(name.prettyPrint(),' ====== ',val.prettyPrint())
errorIndication, errorStatus, errorIndex, varBinds = next(g)
for name,val in varBinds:
print(name.prettyPrint(),' ====== ',val.prettyPrint())
errorIndication, errorStatus, errorIndex, varBinds = next(g)
for name,val in varBinds:
print(name.prettyPrint(),' ====== ',val.prettyPrint())
Запускаем, смотрим результат:
<generator object nextCmd at 0x7f960364f8e0>Переменная g у нас генератор. О чём нам python сразу и говорит, когда выполняет print(g).
SNMPv2-SMI::mib-2.4.20.1.1.10.239.192.2 ====== 10.239.192.2
SNMPv2-SMI::mib-2.4.20.1.1.10.239.192.90 ====== 10.239.192.90
SNMPv2-SMI::mib-2.4.20.1.1.11.0.0.1 ====== 11.0.0.1
SNMPv2-SMI::mib-2.4.20.1.2.10.239.192.2 ====== 36
SNMPv2-SMI::mib-2.4.20.1.2.10.239.192.90 ====== 1
А далее, с помощью встроенной функции next, скрипт перебирает OID за OID. Значения ошибок складываются в errorIndication, errorStatus, errorIndex, а результат складывается в список varBinds. Для получения значений из varBinds используем цикл for:
for name,val in varBinds:
print(name.prettyPrint(),' ====== ',val.prettyPrint())
Про генераторы в python написано много интересных статей, в том числе и на хабре, поэтому не буду углубляться в эту тему.
А теперь у нас получается очень простая последовательность действий:
- Получаем адреса по OID 1.3.6.1.2.1.4.20.1.1, перебирая с помощью next, записываем адреса в список. Для примера будем отслеживать адрес 10.239.192.90.
- Получаем ifindex по OID 1.3.6.1.2.1.4.20.1.2, записываем в список. Для адреса 10.239.192.90 значение ifindex интерфейса равно 1.
- указанием значения ifindex. Для адреса 10.239.192.90 итоговая конструкция будет 1.3.6.1.2.1.31.1.1.1.18.1.
- Собираем названия интерфейсов, используя OID 1.3.6.1.2.1.31.1.1.1.1+.ifindex.
Осталось собрать в функцию с snmpgetnext первые два пункта, а остальные два пункта соберём с помощью snmpget. Функция будем сама ходить по OID, перебирать в них значения и останавливаться, когда все OID закончились. OID будем загружать списком. Список с OID закончился — заканчиваем работу. Результат будет возвращаться двумерным списком.
def snmp_getnextcmd_next(community, ip, port, OID, file):
# метод обрабатывает class generator от def snmp_getnext
# OID - это список OID в виде list_OID = [OID_ipAdEntAddr,OID_ipAdEntIfIndex,OID_ipAdEntNetMask], где переменные строковые значения
# в виде '1.2.3.4'
# возвращаем двумерный список со значениями, по количеству OID
list_result = [] # для формирования списков первого уровня
list_result2 = [] # итоговый список
g = (snmp_getnextcmd(community, ip, port, OID[0])) #начинаем с первого OID
varBinds = 0
flag = True
for oid in list_OID:
if varBinds != 0:
for name, val in varBinds:
list_result2.append(list_result)
list_result = []
list_result.append(val.prettyPrint())
i = 0
while i <= 0: # по списку
errorIndication, errorStatus, errorIndex, varBinds = next(g)
if errors(errorIndication, errorStatus, errorIndex, ip_address_host, file):
if str(varBinds).find(oid) != -1:
i = 0
for name, val in varBinds:
list_result.append(val.prettyPrint())
else:
i = i + 1
flag = False
else:
file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Error snmp_getnextcmd_next ip = ' + ip + ' OID = '+ OID[0] + '\n')
print('Error snmp_getnextcmd_next ', False)
i = i + 1
flag = False
list_result2.append(list_result)
return list_result2
Получаем список, содержащий ip адреса, ifindex, маски. Маски остались после экспериментов, тяжёлое наследие прошлого. Чтобы приступить к следующим этапам по сбору имён интерфейсов, description, необходимо отфильтровать полученные данные. Например, оборудование Huawei, Cisco будут отдавать адреса из сети 127.0.0.0/8, формировать для них доменные имена бессмысленно. В текущей версии скрипта идёт фильтрация по допустимому диапазону IPv4, всё не попадающее в диапазон — удаляется. Очевидно, что для ваших сетей правила фильтрации будут другие.
Собираем имена интерфейсов, description с помощью snmpget и приступаем к формированию файлов зон. Для формирования файла обратных зон необходимо изменить порядок следования октетов, для чего можно воспользоваться reverse_pointer из библиотеки ipaddress или написать что-то своё.
print(ipaddress.ip_address('194.4.5.1').reverse_pointer)У меня скрипт именует файлы обратных зон основываясь на адресах, которые он получает из маршрутизатором, он создаёт словарь, в котором ключ состоит из двух первых октетов ip-адреса. Потом из этих ключей формируются названия файлов. Для этого всё равно необходимо разбирать адрес по октетам. Прикладываю скрипт, который получив список адресов маршрутизаторов должен выдать корректные файлы зон. У меня он именно это делает.
1.5.4.194.in-addr.arpa
Вроде я про все необходимые кубики для начала работы с оборудованием по snmp с помощью python рассказал. Кубики можно комбинировать как угодно, а можно сделать свои.
# скрипт берёт адреса или с базы данных, или из файла.
# скрипт делает первый запрос по snmp sysname, в процессе срабатывает обработчик ошибок, если были ошибки, адрес пропускается
# если сбор sysname прошёл успешно, запрашиваются адреса, ifindex, маски (маски не нужны, это осталось со времени отработки запросов) и тд
# если хост вырубится между запросами - позже будет вываливание скрипта с traceback, так как в testresult будет каша
# на запросах desc и name interface - обработчик работает
# в bind должны быть опция check-names master ignore
# заголовок файлов зон не формируется скриптом, он должен быть в отдельном файле. По умолчанию - named.soa. Скрипт только проставляет $INCLUDE
#import section
from pysnmp.hlapi import *
from ipaddress import *
from datetime import datetime
import mysql.connector
from mysql.connector import errorcode # для работы с mysql неоходимо поставить mysql.connector для python3
# по умолчанию данные из mysql, как пепеключить на файл смотри ниже. формат файла - одна строка один ip адрес в десятичной форме по октетам с разделителем точкой
# var section
#snmp
community_string = 'community' # community string
port_snmp = 161
OID_ipAdEntAddr = '1.3.6.1.2.1.4.20.1.1' # From SNMPv2-MIB ip адреса
OID_ifNumber = '1.3.6.1.2.1.2.1.0' # From RFC1213-MIB количество интерфейсов ifindex
OID_sysName = '1.3.6.1.2.1.1.5.0' # From SNMPv2-MIB hostname/sysname
OID_ipAdEntIfIndex = '1.3.6.1.2.1.4.20.1.2' # From SNMPv2-MIB ifindex interface
OID_ipAdEntNetMask = '1.3.6.1.2.1.4.20.1.3' # From SNMPv2-MIB
OID_ifAlias = '1.3.6.1.2.1.31.1.1.1.18' # Desc интерфейса. для получения к OID надо добавить ifindex
OID_ifName = '1.3.6.1.2.1.31.1.1.1.1' # название интерфейса к OID надо добавить ifindex
list_OID = [OID_ipAdEntAddr,OID_ipAdEntIfIndex,OID_ipAdEntNetMask]
#log
filename_log = 'zone_gen.log' # для лог файла
log_level = 'debug' # ('normal'), ('debug'), ('min')
# bind zone files
file_direct_zone = 'spd.esrr.rzd.hosts' # имя файла прямой зоны
domain = 'domain.name.org' # domainname
named_soa = '$INCLUDE /var/bind/named.soa'
direct_zone =[] # список для прямой зоны
reverse_zone = {} # словарь для обратных зон
name_server_record = 'dns.server.ru. IN A 10.111.33.21\n'
ip_segment_ds = ['10.0.0.0/8'] # для убирания адресов, не попадающих в диапазон
#var section for mysql
username_db = 'username' # логин для mysql
password_db = 'password' # пароль для mysql
db = 'name_database' # имя базы данных
query_from_db = 'select ip from devices where type = 2 or type = 3;' # пример sql запроса;
host = '192.11.33.123' # адрес базы
# ip from file
filename_of_ip = 'ip.txt' # имя файла с Ip адресами
select_source_ip = 'file' # Флаг откуда брать данные из файла ('file') или confstractor ('mysql')
# function section
def snmp_getnextcmd(community, ip, port, OID):
# type class 'generator' errorIndication, errorStatus, errorIndex, result[3]
# метод next для получения значений по порядку, однго за другим с помощью next()
return (nextCmd(SnmpEngine(),
CommunityData(community),
UdpTransportTarget((ip, port)),
ContextData(),
ObjectType(ObjectIdentity(OID))))
def snmp_getcmd(community, ip, port, OID):
# type class 'generator' errorIndication, errorStatus, errorIndex, result[3] - список
# метод get получаем результат обращения к устойстройству по SNMP с указаным OID
return (getCmd(SnmpEngine(),
CommunityData(community),
UdpTransportTarget((ip, port)),
ContextData(),
ObjectType(ObjectIdentity(OID))))
def snmp_get_next(community, ip, port, OID, file):
# метод обрабатывает class generator от def snmp_get
# обрабатываем errors, выдаём тип class 'pysnmp.smi.rfc1902.ObjectType' с OID и значением
# получаем одно скалярное значение
errorIndication, errorStatus, errorIndex, varBinds = next(snmp_getcmd(community, ip, port, OID))
# тут должен быть обработчик errors
x = []
if errors(errorIndication, errorStatus, errorIndex, ip, file):
for name, val in varBinds:
return (val.prettyPrint(), True)
else:
file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : Error snmp_get_next ip = ' + ip + ' OID = ' + OID + '\n')
return ('Error', False)
def errors(errorIndication, errorStatus, errorIndex, ip, file):
#обработка ошибок В случае ошибок возвращаем False и пишем в файл (нереализовано)
if errorIndication:
print(errorIndication, 'ip address ', ip)
file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + str(errorIndication) + ' = ip address = ' + ip + '\n')
return False
elif errorStatus:
print(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + '%s at %s' % (errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + '%s at %s' % (errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or '?' + '\n'))
return False
else:
return True
def snmp_getnextcmd_next(community, ip, port, OID, file):
# метод обрабатывает class generator от def snmp_getnext
# OID - это список OID в виде list_OID = [OID_ipAdEntAddr,OID_ipAdEntIfIndex,OID_ipAdEntNetMask], где переменные строковые значения
# в виде '1.2.3.4'
# возвращаем двумерный список со значениями, по количеству OID
list_result = [] # для формирования списков первого уровня
list_result2 = [] # итоговый список
g = (snmp_getnextcmd(community, ip, port, OID[0])) #начинаем с первого OID
varBinds = 0
flag = True
for oid in list_OID:
if varBinds != 0:
for name, val in varBinds:
list_result2.append(list_result)
list_result = []
list_result.append(val.prettyPrint())
i = 0
while i <= 0: # по списку
errorIndication, errorStatus, errorIndex, varBinds = next(g)
if errors(errorIndication, errorStatus, errorIndex, ip_address_host, file):
if str(varBinds).find(oid) != -1:
i = 0
for name, val in varBinds:
list_result.append(val.prettyPrint())
else:
i = i + 1
flag = False
else:
file.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Error snmp_getnextcmd_next ip = ' + ip + ' OID = '+ OID[0] + '\n')
print('Error snmp_getnextcmd_next ', False)
i = i + 1
flag = False
list_result2.append(list_result)
return list_result2
def check_ip(ip): # проверка ip адреса
try:
ip_address(ip)
except ValueError:
return False
else:
return True
def check_ip2(ip): # проверка ip адреса
try:
ip_interface(ip)
except ValueError:
return False
else:
return True
def get_from_mysql(host, user, password, db_name, query, fd):
flag = True
try:
cnx = mysql.connector.connect(user=user, password = password, host = host ,database = db_name)
if cnx.is_connected():
print('Connected to MySQL database')
fd.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Connected to MySQL database '+ host + '\n')
cursor1 = cnx.cursor()
cursor1.execute(query)
query_result = []
i=0
list = []
for var in cursor1:
query_result.append(str(var)[1:-2])
for var in query_result:
# проверяем, что то что отдал констрактор это ip адрес а не мусор
if var.isdigit():
if check_ip(int(var)):
list.append(str(ip_address(int(var))))
# query_result.append(str(ip_address(int(str(var)[1:-2]))))
except mysql.connector.Error as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
print("Something is wrong with your user name or password")
fd.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + "Something is wrong with your user name or password" + '\n')
flag = False
elif err.errno == errorcode.ER_BAD_DB_ERROR:
print("Database does not exist")
fd.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + "Database does not exist" + '\n')
flag = False
else:
print(err)
fd(err)
flag = False
cursor1.close()
cnx.close()
return (list, flag)
def get_from_file(file, filelog): # выбирает ip адреса из файла. одна строка - один адрес в десятичной форме
fd = open(file,'r')
list_ip = []
for line in fd:
line=line.rstrip('\n')
if check_ip(line):
list_ip.append(line)
else:
filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ': Error Мусор в источнике ip адресов ' + line)
fd.close()
return list_ip
# code section
#открываем лог файл
filed = open(filename_log,'w')
# записываем текущее время
filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + '\n')
#получаем список адресов с базы
if select_source_ip == 'file':
ip_from_confstractor = get_from_file(filename_of_ip, filed)
else:
if select_source_ip == 'mysql':
ip_from_confstractor, flag_mysql = get_from_mysql(host , username_db, password_db, db, query_from_db, filed)
if log_level == 'debug':
filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'ip address from ' + select_source_ip + '\n')
for var in ip_from_confstractor:
filed.write(var + '\n')
# получаем общее количество ifindex оказалось не нужно
#number_ifindex = (snmp_get_next(community_string, ip_address_host, port_snmp, OID_ifNumber))
for ip_address_host in ip_from_confstractor:
# получаем sysname hostname+domainname, флаг ошибки
sysname, flag_snmp_get = (snmp_get_next(community_string, ip_address_host, port_snmp, OID_sysName, filed))
if flag_snmp_get:
# Всё хорошо, хост ответил по snmp
if sysname == 'No Such Object currently exists at this OID' :
# а community неверный.надо пропускать хост, иначе словим traceback. Причём ты никак не поймаешь, что проблема в community, поэтому всегда надо запрашивать hostname, который отдают все устройства
print('ERROR community', sysname , ' ' , ip_address_host)
filed.write(datetime.strftime(datetime.now(),
"%Y.%m.%d %H:%M:%S") + ' : ' + 'ERROR community sysname = ' + sysname + ' ip = ' + ip_address_host + '\n')
else:
if log_level == 'debug':
filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Был получен sysname ' + sysname + ' type ' + str(type(sysname)) + ' len ' + str(len(sysname)) + ' ip ' + ip_address_host + '\n')
if len(sysname) < 3:
sysname = 'None_sysname'
if log_level == 'debug' or log_level == 'normal':
filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Error sysname короче 3 символов = ' + sysname + ' ip = ' + ip_address_host + '\n')
if sysname.find(domain) == -1:
# что-то отдало hostname без домена, например Huawei или Catos
sysname = sysname + '.' + domain
# print("check domain хост не отдал домен", sysname, " ", ip_address_host , "\n")
if log_level == 'debug' or log_level == 'normal':
filed.write("check domain хост не отдал домен: " + sysname + " " + ip_address_host + " " + "\n")
else:
# это условие по уровню записи в лог
pass
#получаем адреса, ifindex, маски грузим в двумерный список
# testresult[0] = адреса
# testresult[1] = ifindex
# testresult[2] = маски
testresult = snmp_getnextcmd_next(community_string, ip_address_host, port_snmp, list_OID, filed)
i = 0
for var in testresult[0]: # убираем адреса не попадающие в ip_segment_ds
flag = 'False'
for net in ip_segment_ds:
if (IPv4Address(var) in IPv4Network(net)):
flag = 'True'
if flag == 'True':
pass
else:
testresult[0].pop(i)
testresult[1].pop(i)
i = i +1
# зная ifindex, заполняем desc интерфейсов, добавляем отдельным списком, меняем пробелы и дефисы на подчеркивания
# testresult[3] = desc
desc = []
i = 0
for var in testresult[1]:
x, flag_snmp_get = snmp_get_next(community_string, ip_address_host, port_snmp, OID_ifAlias+'.'+var, filed)
if flag_snmp_get:
x = x.replace('"', '').replace(' ', '_').replace('(', '').replace(')', '').replace(
',', '.').replace(']','').replace('[','').replace("'","").replace('-_','').replace('_-','')
desc.append(x)
i = i + 1
else:
i = i +1
testresult[1].pop(i) # если хост не ответил на запрос desc по ifindex, не переспрашиваем, а убираем ifindex из списка
# так как данные для него собрать не удалось. соответственно имя интерфейса для него запрашивать не будем.
testresult[0].pop(i) # адрес тоже убираем, иначе произойдёт путаница при сборе файлов зон
testresult.append(desc) # добавляем третьим
# зная ifindex, собираем имена интерфейсов
desc = []
for var in testresult[1]:
x, flag_snmp_get = snmp_get_next(community_string, ip_address_host, port_snmp, OID_ifName + '.' + var, filed)
if flag_snmp_get:
x = x.replace('Loopback', 'Lo').replace('loopback0', 'Lo0').replace(' ', '_')
desc.append(x)
i = i + 1
else:
i = i + 1
testresult[1].pop(i) # если хост не ответил на запрос имени интерфейса по ifindex, не переспрашиваем, а убираем ifindex из списка
# так как данные для него собрать не удалось.
testresult[0].pop(i) # адрес тоже убираем, иначе произойдёт путаница при сборе файлов зон или tracebrake получим
testresult[3].pop(i) # desc убираем, иначем произойдёт путаница при сборе файлов зон или tracebrake получим
testresult.append(desc) # добавляем четвёртым
#testresult[4] названия интерфейсов
i = 0
for var in testresult[3]:
if var == 'No_Such_Object_currently_exists_at_this_OID': # для APC и убогих Zyxel, не поддерживают OID desc
testresult[3][i] = ''
testresult[4][i] = 'Lo0'
i = i + 1
if log_level == 'debug':
for var in testresult:
filed.write(str(var) + '\n')
# формируем данные для файлов зон
i=0
for var in testresult[0]:
rev = var.split('.')
revs = rev[:]
revs.reverse()
revsstr='.'.join(revs)
# заполняем словарь обратных зон
if (rev[0]+'.'+rev[1]) in reverse_zone:
if testresult[4][i] == 'Lo0':
reverse_zone[rev[0]+'.'+rev[1]].append(revsstr + '.in-addr.arpa. IN PTR ' + sysname + '.')
else:
reverse_zone[rev[0] + '.' + rev[1]].append(revsstr + '.in-addr.arpa. IN PTR ' + testresult[4][i] + '.' + testresult[3][i] + '.' + sysname + '.')
else:
reverse_zone[rev[0]+'.'+rev[1]] = []
if testresult[4][i] == 'Lo0':
reverse_zone[rev[0] + '.' + rev[1]].append(revsstr + '.in-addr.arpa. IN PTR ' + sysname + '.')
else:
reverse_zone[rev[0] + '.' + rev[1]].append(
revsstr + '.in-addr.arpa. IN PTR ' + testresult[4][i] + '.' + testresult[3][
i] + '.' + sysname + '.')
# заполняем список прямой зоны
if testresult[4][i] == 'Lo0':
direct_zone.append(sysname + '.' + ' IN A ' + var)
elif testresult[3][i] == '':
direct_zone.append(testresult[4][i]+'.' + sysname + '.' +' IN A ' + var)
else:
direct_zone.append(testresult[4][i] + '.' + testresult[3][i] + '.' + sysname + '.' + ' IN A ' + var)
i=i+1
else:
print('Error хост не ответил при запросе ip ', ip_address_host)
filed.write(datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + ' : ' + 'Error хост не ответил при запросе snmp, ip хоста ' + ip_address_host + ' ' + str(flag_snmp_get) + '\n')
# записываем всё собранное в файлы
f=open(file_direct_zone, 'w') # файл прямой зоны
f.write(named_soa + '\n'+'\n')
f.write(name_server_record)
for i in direct_zone:
i = i.replace('..','.')
f.write(i + '\n')
f.close()
file_list_reverse = list(reverse_zone.keys())
print(file_list_reverse)
if log_level == 'debug':
for i in file_list_reverse:
filed.write(i + '\n')
for i in list(reverse_zone.keys()): # записываем файлы обратных зон
f=open(i+'.rev','w')
f.write(named_soa + '\n'+'\n')
for y in reverse_zone[i]:
y = y.replace('..', '.')
f.write(y + '\n')
f.close()
# пишем в лог время
filed.write('\n' + datetime.strftime(datetime.now(), "%Y.%m.%d %H:%M:%S") + '\n')
#закрываем лог файл
filed.close()
P.S.: Ссылки
- Про SNMP:
Wiki Debian про SNMP
Хорошая статья «SNMP MIBs и как их готовить»
- Про mib браузеры:
MIB browser от iReasoning
В бесплатной версии одновременно можно работать только с 10 MIB.
OpManager FREE MIB Browser
- Про Python:
Скрипт плохо соответствует требованиям PEP8 Style Guide for Python Code, что не очень хорошо.
Если вы желаете красиво и правильно собирать логи с ваших скриптов, то вам сюда logging — Logging facility for Python
Комментарии (19)
ALexhha
15.10.2017 01:40Я вот только не пойму, какие именно преимущества даёт питон, по сравнению с тем же bash, конкретно в данных примерах?
zw_irk Автор
15.10.2017 03:23Я думаю, что всем будем интересно, если вы реализуете решение это задачи на bash и напишите статью.
kvaps
15.10.2017 12:32+1Ну я для подобных вещей использую ansible со стандартными модулями.
Причем если данные собранные ansible нужно сильно модифицировать перед отправкой, не вижу проблем использоватьlocal_action
сshell
модулем, где данные можно подготовить с помощью того bash (или python).
В итоге получаем универсальный playbook с простыми набором задач, где каждая задача делает своё конкретное действие:
- собирает данные
- генерирует новые данные на основе полученных
- записывает конфиг или генерит команды
- отправляет конфиг или удаленно исполняет команды
На данный момент, мне кажется что это решение идеальное во всем. Так как оно универсальное, в нем легко разобраться и оно отлично работает.
PS: Вы не подумайте, мне очень интересна ваша статья и я определенно нашел в ней много полезного для себя. Но в силу того что я не очень хорошо знаю python, я бы реализовал это именно так.
А каково ваше мнение на счёт ansible?zw_irk Автор
15.10.2017 14:48+1Я в первой статье упоминал и про ansible, и про puppet, и про модули для них от вендоров сетевого оборудования. Далее в планах начать рассказ про использование ansible в сопровождении сетевого оборудования:)
evseev
15.10.2017 07:43+1Ответ зависит от ваших наклонностей и желаний. Если вам нравится писать код на хорошем и удобном языке с множеством возможностей в удобной IDE, которая делает примерно треть вашей работы, то преимущества налицо. Если же вы предпочитаете только подсветку синтаксиса и все писать своими руками на языке с целым рядом ограничений, то преимуществ нет.
ALexhha
15.10.2017 13:28Я, конечно, может что-то не понимаю, но зачем для таких примитивных задач еще и IDE? А то получается — пишем на питоне потому что стильно/модно/молодежно. Я сам использую питон и люблю его, но в контексте данной статьи я не вижу какие преимущества он дает сетевикам в задачах простейшего парсинга вывода.
zw_irk Автор
15.10.2017 14:51в задачах простейшего парсинга вывода.
Не совсем понял, парсинг вывода чего вы имеет ввиду? Скрипт не пользуется внешними утилитами.ALexhha
15.10.2017 15:22Я про изначальную задачу ТС.
Необходимо сформировать доменные имена для маршрутизаторов и их интерфейсов. Скрипт должен из подобной конфигурации сформировать две записи, для прямой зоны и для обратной зоны
А по сути обычный парсинг вывода snmpwalk
fkvf
за книжку спасибо, давно не общался с Наташей, а она уже и книжки начала писать :)zw_irk Автор
15.10.2017 15:25Скрипт не использует для своей работы внешние утилиты и, соответственно, ничего не парсит.
ALexhha
15.10.2017 16:58Скрипт не использует для своей работы внешние утилиты и, соответственно, ничего не парсит.
т.е. по-вашему функция snmp_getnextcmd_next это не парсинг?zw_irk Автор
15.10.2017 17:36Она собирает данные по snmp, перебирая полученные nextом OIDы и вылавливает ошибки. Полученные данные она складывает в список. Что вы называете парсингом?
evseev
17.10.2017 19:25IDE что бы экономить время. Мы же не про программиста говорим для которого программа- основная часть работы. Для сетевика это скорее побочная нагрузка.
fkvf
15.10.2017 10:57+1Python для сетевых инженеров. Книга на gitbook
www.gitbook.com/book/natenka/pyneng/details
kovserg
Как же мне всегда нравилось: для решения простой задачи надо написать кучу кода.
SMNP = Simple Network Management Protocol
Видимо слово Simple означает нечто большее чем «просто».
ps: Сетевеки особо не заморачиваются используют что-нибудь готовое типа PRTG
zw_irk Автор
Подскажите, что готовое позволяет формировать файлы зон для Bind?
evseev
Даже простая задача требует внимания и каждая ошибка может дорого обойтись. Что примечательно. Скрипты не ошибаются, они не бывают уставшими и никогда не бывают сонными. И что касается рутины- они далеко впереди человека. Так что лучше скрипты, чем руками.
PS: а где вы видите кучу кода?