- PVSM.RU - https://www.pvsm.ru -

Зачем сетевику Python? Часть вторая

В первой части Зачем сетевику Python [1] скрипт научился собирать hostname оборудования по SNMP. К сожалению, по некоторым причинам вторая часть, мягко говоря, подзадержалась, но в ней я расскажу, как собрать и обработать остальные данные, необходимые для формирования файлов зон.

Сформулируем задачу, которую надо решить: необходимо сформировать доменные имена для маршрутизаторов и их интерфейсов. Скрипт должен из подобной конфигурации (в данном случае 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

sudo apt-get install snmp-mibs-downloader

Если необходимо, чтобы утилиты работали с именованными OID в конфигурационном файле /etc/snmp/snmp.conf добавляем используемые mib или ключевое слово ALL.
Конечно, очевидно, что использовать ALL не рекомендуется, количество файлов огромно, поиск по всем файлам подряд во многих случаях явно не очень хорошая идея. Однако, в случае написания программы и поиска нужного MIB файла это допустимо. Не забудьте только потом оставить только нужное и необходимое.

$ cat /etc/snmp/snmp.conf
# 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:~$

Проверяем, увидел snmp наши MIB:

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
IF-MIB::ifAlias.1 = STRING: Line_to_OUT

Последняя цифра (1), которую я добавил — это ifindex. Ifndex это номер интерфейса, назначенный snmp агентом операционной системы оборудования.

Нумерация производится при инициализации агента (при загрузке ОС). После каждой перезагрузке оборудования она будет другой, если вы не укажет маршрутизатору запоминать назначенные ifindex (гуглить по ключевым словам ifindex persist).

В работе с интерфейсами всё логично, берём OID, добавляем ifindex, делаем запрос – получаем description интерфейса с этим Ifindex.

Запросим snmpwalk, который запросит с роутера все доступные OIDы и их текущие значения:

host$ snmpwalk -v 2c -c tilitili 10.239.192.2

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:

В выводе snmpwalk есть что почитать, он очень многословен, поэтому его лучше фильтровать.

Можно обратить внимание, интерфейсы 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

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
и так далее

И из вывода сразу видно, что нет OID, добавив к которому ifindex, мы бы получали ip адреса интерфейса с этим ifindex. Обратите внимание:

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
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

И так далее. Обратите внимание, мы не знаем адреса маршрутизатора при первом запросе, поэтому первое обращение идёт просто на значение OID 1.3.6.1.2.1.4.20.1.1

Попробуем автоматизировать эти запросы. Напишем функцию и вызовем её несколько раз:

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>
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

Переменная g у нас генератор. О чём нам python сразу и говорит, когда выполняет print(g).
А далее, с помощью встроенной функции next, скрипт перебирает OID за OID. Значения ошибок складываются в errorIndication, errorStatus, errorIndex, а результат складывается в список varBinds. Для получения значений из varBinds используем цикл for:

for name,val in varBinds:
        print(name.prettyPrint(),' ====== ',val.prettyPrint())

Про генераторы в python написано много интересных статей, в том числе и на хабре, поэтому не буду углубляться в эту тему.

А теперь у нас получается очень простая последовательность действий:

  1. Получаем адреса по OID 1.3.6.1.2.1.4.20.1.1, перебирая с помощью next, записываем адреса в список. Для примера будем отслеживать адрес 10.239.192.90.
  2. Получаем ifindex по OID 1.3.6.1.2.1.4.20.1.2, записываем в список. Для адреса 10.239.192.90 значение ifindex интерфейса равно 1.
  3. указанием значения ifindex. Для адреса 10.239.192.90 итоговая конструкция будет 1.3.6.1.2.1.31.1.1.1.18.1.
  4. Собираем названия интерфейсов, используя 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)

1.5.4.194.in-addr.arpa

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

Вроде я про все необходимые кубики для начала работы с оборудованием по 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.21n'

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 [2]
Хорошая статья «SNMP MIBs и как их готовить» [3]

  • Про mib браузеры:

MIB browser от iReasoning [4]
В бесплатной версии одновременно можно работать только с 10 MIB.
OpManager FREE MIB Browser [5]

  • Про Python:

Скрипт плохо соответствует требованиям PEP8 Style Guide for Python Code [6], что не очень хорошо.
Если вы желаете красиво и правильно собирать логи с ваших скриптов, то вам сюда logging — Logging facility for Python [7]

Автор: Вадим Максимович

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/265694

Ссылки в тексте:

[1] Зачем сетевику Python: https://habrahabr.ru/post/333614/

[2] Wiki Debian про SNMP: https://wiki.debian.org/SNMP

[3] Хорошая статья «SNMP MIBs и как их готовить»: https://habrahabr.ru/post/206612/

[4] MIB browser от iReasoning: http://ireasoning.com/mibbrowser.shtml

[5] OpManager FREE MIB Browser: https://www.manageengine.com/products/mibbrowser-free-tool/download.html

[6] PEP8 Style Guide for Python Code: https://www.python.org/dev/peps/pep-0008/

[7] logging — Logging facility for Python: https://docs.python.org/3/library/logging.html

[8] Источник: https://habrahabr.ru/post/340050/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best