Получение соответствия имени ПК и портов свитчей

Материал из Mindsellers
Перейти к: навигация, поиск

Python

Перед нами стоит следующая задача: определить, к какому порту и какого коммутатора подключен пользовательский ПК. Или БЫЛ подключен, если он уже отвалился. Ситуация осложнена тем, что все получают адреса по dhcp. Но упрощается тем, что мы имеем на access управляемые коммутаторы, причем более-менее однородные. Было принято решение реализовать сбор информации со свитчей черeз snmp, а именно получить соответствие порта и мака, который "светится" за ним. Затем, используя nmap, получим информацию от всех доступных на данный момент ПК с соответствием имени, адреса и мака.

Вот что получилось:

Get.png

Нам потребуется:

1. MySql

2. Python

3. pymysql

4. nmap

5. snmpwalk из пакета snmp

Для начала создадим базу и таблицы:

база называется switch, содержит таблицы sw(switch,port,mac,date) и pc(name,ip,mac). Все поля текстовые, в таблице pc все уникальные, в switch уникальный mac, а date - автоназначаемая CURRENT_DATETIME

Создадим два файла: sw_list.txt, куда занесем список опрашиваемых свитчей, причем, сначала перечислим "корневые", а потом периферийные(мак-то в таблице уникальный, так что пусть периферийный свитч перезапишется вместо головного), и net_list.txt, куда занесем сканируемые подсети с компьютерами в сокращенном виде, например, 10.0.0.0/24

Далее, напишем скрипт-сборщик информации:

import pymysql
import subprocess
import traceback

nmap='/usr/bin/nmap'
snmp='/usr/bin/snmpwalk'
sw_list='sw_list.txt'
net_list='net_list.txt'
def mysql(table,field1,field2,field3,val1,val2,val3):
        connect=pymysql.connect(host="10.10.1.33", user="switch", passwd="switch", db="switch")
        cursor=connect.cursor()
        sql="replace into "+table+"("+field1+","+field2+","+field3+") values (\'"+val1+"\',\'"+val2+"\',\'"+val3+"\')"
        cursor.execute(sql)
        connect.commit()
        connect.close()


sw_list=open(sw_list).read().splitlines()

for switch in sw_list:

        try:


                snmp_resp_all=subprocess.check_output([snmp,'-c', 'public', '-v2c' , switch, '1.3.6.1.2.1.17.4.3.1.2']).splitlines()
                ports=[]
                macs=[]
                for line in snmp_resp_all:

                        macs.append('{:02X}'.format(int(line.split('.')[11],10))+'{:02X}'.format(int(line.split('.')[12],10))+'{:02X}'.format(int(line.split('.')[13],10))+'{:02X}'.format(int(line.split('.')[14],10))+'{:02X}'.format(int(line.split('.')[15],10))+'{:02X}'.format(int(line.split('.')[16].split(' ')[0],10)))
                        ports.append(line.split(' ')[3])
                for i in range(len(ports)):
                        if ports.count(ports[i]) < 24:

                                mysql('sw','switch','port','mac',switch,ports[i],macs[i])
        except:
                traceback.print_exc()
                print 'Cannot connect to '+switch




net_list=open(net_list).read().splitlines()
for net in net_list:
        map=subprocess.check_output([nmap, "-sP", net]).splitlines()
        for line in map:
                if line.startswith('Nmap scan report for'):
                        if len(line.split(' ')) > 5:
                                name=line.split(' ')[4].split('.')[0]
                                ip=line.split(' ')[5][1:-1]
                        else:
                                name=line.split(' ')[4]
                                ip=name
                        mac=''
                if line.startswith('MAC Address'):
                        mac=line[13:].split(' ')[0].replace(':','')
                        try:
                                mysql('pc','name','ip','mac',name,ip,mac)
                        except:
                                print 'mysql error'


Как несложно заметить, в самом начале мы задали пути к необходимым приложениям и указали 2 файла: список сетей (10.0.0.0/24 с новой строки каждая) и список свитчей(10.0.0.0 с новой строки), а также указали параметры подключения к БД.

Стоит пояснить принцип его работы.

Пробегаясь по списку свитчей, начиная с корневого, мы посылаем snmp-запрос OID 1.3.6.1.2.1.17.4.3.1.2, который возвращает несколько строк, по количеству известных свитчу маков, например, такую:

iso.3.6.1.2.1.17.4.3.1.2.100.81.6.39.35.106 = INTEGER: 49

Выделенные значения - это мак-адрес, только отображенный в десятичной системе счисления, а последнее число - номер порта. Мы проверяем, сколько маков на каждом порту, и если их не больше 24(то есть, отделяем тупые свитчи от транковых портов) и пишем в базу.

После проверки всех свитчей, натравливаем nmap на список подсетей и получаем строки вида

Nmap scan report for it.domain.local (10.10.10.163)
Host is up (0.00033s latency).
MAC Address: 10:7B:44:8E:15:7A (Unknown)

которые успешно разбираем на необходимые составляющие (имя, адрес, мак) и отправляем в таблицу

Этот скрипт запихиваем в cron на выполнение, допустим, раз в пару часов и пишем скрипт для просмотра информации.

Теперь пора научиться получать данные и написать для этого скрипт. Его суть проста: принимаем на вход или имя компьютера, или IP, или адрес свитча(с портом или без) и отдаем, соответственно, или расположение нужного ПК в сети или информацию по порту/портам на конкретном свитче.

Но сотрудники не очень хотят лазить в консоль, поэтому мы создадим html-страничку:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Обработка данных форм</title>
</head>
<body>
<h4><p>Если вы знаете имя компьютера или IP, введите его в форму ниже, не заполняйте остальные поля</p>

<p>Если нужно посмотреть клиентов на порту - не заполняйте поле Имя/IP</p>

<p>Если хотите посмотреть всех клиентов по свитчу - укажите только свитч</p> </h4>
<table border="1">
<tr><th></th><th></th></tr>
    <form action="/cgi-bin/get.py">
       <tr><th>Имя/IP</th><th><input type="text" name="name"></th></tr>
       <tr><th>Адрес свитча</th><th><input type="text" name="switch"></th></tr>
       <tr><th>Порт свитча</th><th><input type="text" name="port"></th></tr>
        <tr><th></th><th><button type="submit">Искать!</button></th><tr>
    </form>
</table>
</body>
</html>

Скрипт получения информации, который мы вызывали в строке

<form action="/cgi-bin/get.py">

расположим в своем cgi-bin. Путь к cgi-bin задается в конфигах apache

Листинг скрипта ниже

#!/usr/bin/python
# -*- coding: utf-8 -*-
import pymysql
import cgi
def mysql_n(name):
        connect=pymysql.connect(host="10.10.1.33", user="switch", passwd="switch", db="switch")
        cursor=connect.cursor()
        mac_in=''
        if name.count('.') == 3:
                sql="select * from pc where ip=\'"+name+"\'"
        elif name.count(':') == 5:
                name.upper()
                name.replace(":","")
                mac_in=name.replace(':','').upper()
                sql="select * from pc where mac=\'"+name.replace(':','').upper()+"\'"
        else:
                sql="select * from pc where name='"+name+"\'"
        cursor.execute(sql)
        print ("""
                <table border="1">
                <caption><h3>Расположение компьютера</h3></caption>
                <tr>
                <th>Имя</th>
                <th>IP</th>
                <th>Мак</th>
                <th>Свитч</th>
                <th>Порт</th>
                <th>Последнее обнаружение</th>
                </tr>""")

        try:
                name, ip, mac = cursor.fetchall()[0]
                sql2="select * from sw where mac='"+mac+"\'"
                cursor.execute(sql2)
                try:

                        for resss in cursor.fetchall():
                                switch, port, mac, date = resss
                                print "<tr><td>"+ name + '</td><td>'+ip+'</td><td>'+mac+'</td><td><a target="_blank" title="Расположение свитча" href="../../wiki/index.php/Swlocation#'+switch+'">'+switch+'</a></td><td>' + port+ '</td><td>'+str(date)+"</td><tr>"
                                mysql_sw(switch,port)
                except:
                        print "No Switch Found!\n"
                        switch, port = '0','0'


        except:
                print "Not Found!"

        connect.close()
        print '<p><a target="_blank" href="get.py?switch='+switch+'">Все порты свитча</a></p>'


def mysql_sw(switch,port):
        connect=pymysql.connect(host="10.10.1.33", user="switch", passwd="switch", db="switch")
        cursor=connect.cursor()
        j=0
        if port=='':
                sql="select mac,date,port from sw where switch=\'"+switch+"\' order by port,date"

        else:

                sql="select mac,date,port from sw where switch=\'"+switch+"\' and port=\'"+port+"\' order by port,date"
        print '<table border="1">'
        print "<caption><h3>Список клиентов "+ switch +": "+ port + " </h3></caption>"
        print ("""
                <tr>
                <th>Порт</th>
                <th>Мак</th>
                <th>IP</th>
                <th>Имя</th>
                <th>Последнее обнаружение</th>
                </tr>""")
        cursor.execute(sql)
        result1=cursor.fetchall()
        for macdate in result1:
                j=j+1
                sql="select name,ip from pc where mac=\'"+macdate[0]+"\'"
                cursor.execute(sql)
                try:
                        name,ip=cursor.fetchall()[0]
                except:
                        name,ip=' ', ' '
                print "<tr><td>"+macdate[2]+"</td><td>" + macdate[0]+"</td><td>" + ip+ "</td><td>" + name + "</td><td>" + str(macdate[1])+ "</td></tr>"
        print '</table>'
        print '<p><h4>Итого: '+str(j)+'</h4></p>'
        return





form = cgi.FieldStorage()
name = form.getfirst('name') or ''
switch = form.getfirst('switch') or ''
port = form.getfirst('port') or ''

print("Content-type: text/html\n")
print("""<!DOCTYPE HTML>
        <html>
        <head>
            <meta charset="utf-8">
            <title>Analyse</title>
        </head>
        <body>
        <p><h3><a href="../../get.html">ОБРАТНО</a></h3></p>

""")

if name == '':
        mysql_sw(switch,port)
else:
        name=name.lower()
        mysql_n(name)

print ("""

        </table>
         </body>
         </html>""")

exit()


Если отбросить всякие теги, то логика довольно проста: получаем мак из одной таблицы и ищем соответствие во второй.

Все. Теперь коллеги могут легко находить нужный компьютер и смотреть подключенные к порту устройства через простую web-страницу