From 7c4d8dcbb0ff46fda6bed85b96b8b8634d8ddc02 Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Tue, 9 Dec 2014 19:47:57 +0100 Subject: [PATCH] =?UTF-8?q?switchs2.py:=20mode=20"v=C3=A9rification"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/gen_confs/switchs2.py | 202 ++++++++++++++++++++++++++-------- 1 file changed, 158 insertions(+), 44 deletions(-) diff --git a/gestion/gen_confs/switchs2.py b/gestion/gen_confs/switchs2.py index 6059897d..4da31ace 100755 --- a/gestion/gen_confs/switchs2.py +++ b/gestion/gen_confs/switchs2.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- """ Génération de la configuration d'un switch. @@ -26,16 +26,20 @@ from socket import gethostbyname import netaddr import argparse -sys.path.append('/usr/scripts/') +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') import gestion.secrets_new as secrets from lc_ldap.shortcuts import lc_ldap_readonly as make_ldap_conn import gestion.annuaires_pg as annuaire import gestion.config as config - -capture_model = re.compile(r'\((.*)\)') +import lc_ldap.objets as ldap_classes +from gestion.hptools import snmp GIGABIT_MODELS = ['J9021A', 'J9145A'] +MIB_PRISE_VLAN = 'SNMPv2-SMI::enterprises.11.2.14.11.5.1.7.1.15.3.1.1' +MIB_PRISE_MAC = 'SNMPv2-SMI::enterprises.11.2.14.11.5.1.9.4.2' + ldap = make_ldap_conn() # états possibles @@ -43,11 +47,17 @@ V_TAGGED = 1 V_UNTAGGED = 2 V_NO = 3 +# Vlans disponibles +ENABLED_VLANS = ['adherent', 'adm', 'wifi', 'v6only', 'accueil', 'isolement', + 'appts', 'event'] + def vlan_id(name): """Vlan id of a name (filtre jinja)""" return config.vlans[name] def net_of_vlan_name(name): + """Renvoie le nom du réseau (ip) d'un vlan donné. C'est utile pour + lister les IPs de serveurs dhcp à autoriser (entre autres)""" net_name = { 'adherent': 'fil', 'appts': 'personnel-ens', @@ -72,11 +82,20 @@ class Port(object): # : Liste de noms de chambres chambres = None + # : Liste des macs vues + seen_macs = None + + # : Liste des vlans vus + seen_vlans = None + + def __init__(self, num): self.num = num self.servers = list() self.bornes = list() self.chambres = list() + self.seen_macs = list() + self.seen_vlans = list() def __str__(self): if self.uplink: @@ -85,13 +104,13 @@ class Port(object): labels = [] if self.servers: labels.append('Srv_' + - ','.join(s['host'][0].value.split('.',1)[0] + ','.join(s['host'][0].value.split('.', 1)[0] for s in self.servers)) if self.chambres: labels.append('Ch_' + ','.join(self.chambres)) if self.bornes: labels.append('Wifi_' + - ','.join(b['host'][0].value.split('.',1)[0] + ','.join(b['host'][0].value.split('.', 1)[0] for b in self.bornes)) return ",".join(labels) or "Inconnu" @@ -108,6 +127,7 @@ class Port(object): return 'speed-duplex auto-10-100' def flowcontrol(self): + """Est-ce que le flowcontrol est activé sur ce port ?""" if self.uplink or self.servers: return 'no flow-control' else: @@ -188,7 +208,7 @@ class PortList(list): liste.sort() sortie = [] - groupe = [-99999,-99999] + groupe = [-99999, -99999] for x in itertools.chain(liste, [99999]): if x > groupe[1]+1: # Nouveau groupe ! if groupe[0] == groupe[1]: @@ -212,10 +232,128 @@ class PortList(list): untagged.append(port) return (tagged, untagged) +def get_port_dict(switch): + """Renvoie le dictionnaire prise->objet Port""" + # Build ports ! + ports = {} + for num in xrange(1, 1+int(switch['nombrePrises'][0].value)): + ports[num] = Port(num) + + bat, sw_num = get_bat_num(unicode(switch['host'][0])) + # Remplit les machines ayant une prise spécifiée + # (normalement uniquement les serveurs et les bornes) + for machine in ldap.search(u'prise=%s%i*' % (bat, sw_num)): + port = ports[int(machine['prise'][0].value[2:])] + classe = machine['objectClass'][0].value + if classe == 'machineCrans': + port.servers.append(machine) + elif classe == 'borneWifi': + port.bornes.append(machine) + + # On remplit les chambres + for prise, chbres in annuaire.reverse(bat).iteritems(): + # TODO rajouter un arg à reverse + if int(prise[0]) != int(sw_num): + continue + port = ports[int(prise[1:])] + # (below) beware: type(num) == str (ex: 302g) + port.chambres += [bat.upper() + num for num in chbres] + + # Remplit les uplinks + for num_prise, label in annuaire.uplink_prises[bat].iteritems(): + if num_prise/100 != int(sw_num): + continue + port = ports[num_prise % 100] + port.uplink = label + return ports + + +def fill_port_infos(hostname, port_dict): + """Rajoute des infos sur les ports d'un switch""" + + conn = snmp(hostname, version='1', community='public') + + prise_vlan = conn.walk(MIB_PRISE_VLAN, bin_comp=True) + for res in prise_vlan: + res = res.split('.') + port = int(res[-2]) + vlan = int(res[-3]) + port_dict[port].seen_vlans.append(vlan) + + prise_mac = conn.walk(MIB_PRISE_MAC, bin_comp=True) + for mib in prise_mac: + port = int(mib.split('.')[-8]) + mib = mib.split('.') + mac = ':'.join('%02x' % int(mib[i]) for i in xrange(-7, -1)) + port_dict[port].seen_macs.append(mac) + +def check_conf_ldap(hostname): + """Vérifie la conf du switch, la base ldap et les macs/prises associées""" + bat, sw_num = get_bat_num(hostname) + switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0] + + port_dict = get_port_dict(switch) + fill_port_infos(hostname, port_dict) + + for port in port_dict.itervalues(): + print("* Checking port %d (%s)" % (port.num, port)) + # Nombres de vlans + pr_nb_vlans = len(port.seen_vlans) + th_nb_vlans = sum( port.vlan_member(vname) != V_NO for vname in ENABLED_VLANS ) + if not th_nb_vlans and port.radius_auth(): + th_nb_vlans = 1 + if port.bornes and port.vlan_member('adherent') == V_NO: + th_nb_vlans += 1 + if th_nb_vlans != pr_nb_vlans: + print(" Wrong vlan number (%d,%d)" % (th_nb_vlans, pr_nb_vlans)) + print(port.seen_vlans) + print(list( vname for vname in ENABLED_VLANS if port.vlan_member(vname) != V_NO )) + + # Les macs + if port.uplink: + if len(port.seen_macs) < 20: + print(" Uplink but few macs (%d)" % len(port.seen_macs)) + elif port.radius_auth(): # On vérifie que c'est la bonne chambre + th_prises_set = set() + for mac in set(port.seen_macs): + res = ldap.search(u'macAddress=%s' % mac) + if not res: + continue + chbre = unicode(res[0].proprio().get('chbre', ['EXT'])[0]) + th_prise = chbre[0].lower() + th_prise += annuaire.chbre_prises(chbre[0], chbre[1:]) + th_prises_set.add(th_prise) + + pr_prise = bat.lower() + '%d%02d' % (sw_num, port.num) + if th_prises_set and pr_prise not in th_prises_set: + print(" Aucune machine de chbre. Candidats: %r" % th_prises_set) + else: + for mac in set(port.seen_macs): + res = ldap.search(u'macAddress=%s' % mac) + if not res: + print(" Unknown mac %s" % mac) + continue + machine = res[0] + owner = machine.proprio() + if isinstance(owner, ldap_classes.AssociationCrans): + the = unicode(machine.get('prise', ['N/A'])[0]) + the = the.lower() + pra = bat.lower() + '%d%02d' % (sw_num, port.num) + if the != pra: + print(" Machine %s sur mauvaise prise (%s,%s)" % + (machine, the, pra)) + + elif isinstance(machine, ldap_classes.machineWifi): + if not port.bornes: + print(" Machine %s sur prise sans borne ?" % machine) +def get_bat_num(hostname): + """Renvoie un tuple (bat, num) où bat est la lettre du bâtiment et + num l'entier numéro du switch""" + return (hostname[3].lower(), int(hostname[5])) + def conf_switch(hostname): """Affiche la configuration d'un switch""" - bat = hostname[3].lower() - sw_num = int(hostname[5]) + bat, sw_num = get_bat_num(hostname) switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0] @@ -240,8 +378,7 @@ def conf_switch(hostname): 'dhcp_rid_servers': [34, 160], # vlans activés (cf data.vlans) - 'vlan_names': ['adherent', 'adm', 'wifi', 'v6only', 'accueil', - 'isolement', 'appts', 'event'], + 'vlan_names': ENABLED_VLANS, # réseaux où on fait du dhcp snooping (cf data.NETs) 'dhcp_snooping_vlan_names': ['adherent', 'wifi', 'accueil', @@ -275,39 +412,9 @@ def conf_switch(hostname): # Switch avec des ports gigabit uniquement if imodel in GIGABIT_MODELS: data['gigabit'] = True - + # Build ports ! - ports = {} - for x in xrange(1, 1+int(switch['nombrePrises'][0].value)): - ports[x] = Port(x) - - # Remplit les machines ayant une prise spécifiée - # (normalement uniquement les serveurs et les bornes) - for machine in ldap.search(u'prise=%s%i*' % (bat.upper(), sw_num)): - port = ports[int(machine['prise'][0].value[2:])] - classe = machine['objectClass'][0].value - if classe == 'machineCrans': - port.servers.append(machine) - elif classe == 'borneWifi': - port.bornes.append(machine) - - # On remplit les chambres - for prise, chbres in annuaire.reverse(bat).iteritems(): - # TODO rajouter un arg à reverse - if int(prise[0]) != int(sw_num): - continue - port = ports[int(prise[1:])] - # (below) beware: type(num) == str (ex: 302g) - port.chambres += [bat.upper() + num for num in chbres] - - # Remplit les uplinks - for num_prise, label in annuaire.uplink_prises[bat].iteritems(): - if num_prise/100 != int(sw_num): - continue - port = ports[num_prise % 100] - port.uplink = label - - ports_list = PortList(ports.itervalues()) + ports_list = PortList(get_port_dict(switch).itervalues()) data['ports'] = ports_list data['vlans'] = [] # On remplit les objets vlans (nom, id, tagged, untagged etc) @@ -350,7 +457,14 @@ if __name__ == "__main__": "switch.") parser.add_argument('hostname', help="Nom du switch à regénérer " + "(ex: batg-4)") + parser.add_argument('-c', '--check', action='store_true', default=False, + help="Vérifie la conf par rapport aux macs et vlans effectivement" +\ + "présents sur le switch") options = parser.parse_args(sys.argv[1:]) - print(conf_switch(options.hostname)) + if options.check: + check_conf_ldap(options.hostname) + else: + print(conf_switch(options.hostname)) +