Commit 4395076d7fa9d2aac8e71e13f679e50d2f8cb067
Committed by
Eduardo Santos
1 parent
be5b7045
Exists in
master
Várias correções nas coletas das impressoras, incluindo problema que só exportava o último contador.
Showing
5 changed files
with
373 additions
and
17 deletions
Show diff stats
README.md
1 | -cocar-agente | |
1 | +Instalação | |
2 | 2 | ============ |
3 | 3 | |
4 | 4 | Módulo agente coletor para o software Cocar |
... | ... | @@ -11,3 +11,26 @@ Para funcionar é necessário primeiro instalar o pacote da distribuição e só |
11 | 11 | virtualenv --system-site-packages -p /usr/bin/python2.7 cocar-agente |
12 | 12 | </pre> |
13 | 13 | |
14 | + | |
15 | +Operação | |
16 | +================ | |
17 | + | |
18 | +Descrição dos principais comandos de operação | |
19 | + | |
20 | +* Varredura contínua de rede | |
21 | + | |
22 | +<pre> | |
23 | +/srv/cocar-agente/bin/paster scan continous_scan | |
24 | +</pre> | |
25 | + | |
26 | +* Leitura e export do contador das impressoras | |
27 | + | |
28 | +<pre> | |
29 | +/srv/cocar-agente/bin/paster scan printer_scan -t 10000000 | |
30 | +</pre> | |
31 | + | |
32 | +* Coleta de MAC address que não foi inicialmente identificado | |
33 | + | |
34 | +<pre> | |
35 | +/srv/cocar-agente/bin/paster scan scan_mac_all -a eth0 -t 10 | |
36 | +</pre> | |
14 | 37 | \ No newline at end of file | ... | ... |
cocar/commands/scan_commands.py
... | ... | @@ -16,10 +16,11 @@ from ..model.printer import Printer, PrinterCounter |
16 | 16 | from ..model.host import Host |
17 | 17 | from ..model.computer import Computer |
18 | 18 | from ..csv_utils import NetworkCSV |
19 | -from ..session import NmapSession, SnmpSession | |
19 | +from ..session import NmapSession, SnmpSession, ArpSession | |
20 | 20 | from multiprocessing import Process, Queue |
21 | 21 | from ..xml_utils import NmapXML |
22 | 22 | from sqlalchemy.exc import IntegrityError |
23 | +from sqlalchemy import and_ | |
23 | 24 | |
24 | 25 | log = logging.getLogger() |
25 | 26 | |
... | ... | @@ -75,6 +76,12 @@ class ScanCommands(command.Command): |
75 | 76 | help='Arquivo individual de rede para ser carregado' |
76 | 77 | ) |
77 | 78 | |
79 | + parser.add_option('-a', '--iface', | |
80 | + action='store', | |
81 | + dest='iface', | |
82 | + help='Interface de rede para utilizar no Arping' | |
83 | + ) | |
84 | + | |
78 | 85 | def __init__(self, name): |
79 | 86 | """ |
80 | 87 | Constructor method |
... | ... | @@ -134,6 +141,15 @@ class ScanCommands(command.Command): |
134 | 141 | if cmd == 'import_printers': |
135 | 142 | self.import_printers() |
136 | 143 | return |
144 | + if cmd == 'get_mac': | |
145 | + self.get_mac() | |
146 | + return | |
147 | + if cmd == 'scan_mac': | |
148 | + self.scan_mac() | |
149 | + return | |
150 | + if cmd == 'scan_mac_all': | |
151 | + self.scan_mac() | |
152 | + return | |
137 | 153 | else: |
138 | 154 | log.error('Command "%s" not recognized' % (cmd,)) |
139 | 155 | |
... | ... | @@ -286,6 +302,7 @@ class ScanCommands(command.Command): |
286 | 302 | while True: |
287 | 303 | log.info("Iniciando scan de redes...") |
288 | 304 | self.scan_networks() |
305 | + | |
289 | 306 | log.info("Scan de redes finalizado. Iniciando procedimento de " |
290 | 307 | "identificação de ativos de rede, computadores e impressoras") |
291 | 308 | self.load_network_files() |
... | ... | @@ -305,17 +322,31 @@ class ScanCommands(command.Command): |
305 | 322 | log.info("EXPORT DE IMPRESSORAS FINALIZADO!!! Reiniciando as coletas") |
306 | 323 | #time.sleep(600) |
307 | 324 | |
325 | + def scan_mac_all(self): | |
326 | + """ | |
327 | + Fica varrendo a rede tentando arrumar os MAC's | |
328 | + """ | |
329 | + print("*** Aperente CTRL+C para encerrar a execução ***") | |
330 | + | |
331 | + while True: | |
332 | + self.scan_mac() | |
333 | + log.info("SCAN DE MAC FINALIZADO!!!") | |
334 | + | |
308 | 335 | def export_printers(self): |
309 | 336 | """ |
310 | 337 | Exporta todos os contadores para o Cocar |
311 | 338 | """ |
312 | 339 | session = self.cocar.Session |
313 | - results = session.query(Printer).all() | |
340 | + results = session.query(Printer).join( | |
341 | + PrinterCounter.__table__, | |
342 | + PrinterCounter.network_ip == Printer.network_ip | |
343 | + ).all() | |
314 | 344 | for printer in results: |
315 | 345 | log.info("Exportando impressora %s", printer.network_ip) |
316 | 346 | printer.export_printer(server_url=self.cocar.config.get('cocar', 'server_url'), session=session) |
317 | 347 | |
318 | 348 | session.close() |
349 | + log.info("EXPORT DAS IMPRESSORAS FINALIZADO!!! %s IMPRESSORAS EXPORTADAS!!!", len(results)) | |
319 | 350 | |
320 | 351 | def get_printer_attribute(self): |
321 | 352 | """ |
... | ... | @@ -457,6 +488,30 @@ class ScanCommands(command.Command): |
457 | 488 | session = self.cocar.Session |
458 | 489 | for hostname in nmap_xml.hosts.keys(): |
459 | 490 | host = nmap_xml.identify_host(hostname, timeout=self.options.timeout) |
491 | + # Antes de tudo verifica se ele já está na tabela de contadores | |
492 | + counter = session.query( | |
493 | + PrinterCounter.__table__ | |
494 | + ).outerjoin( | |
495 | + Printer.__table__, | |
496 | + PrinterCounter.network_ip == Printer.network_ip | |
497 | + ).filter( | |
498 | + and_( | |
499 | + PrinterCounter.network_ip == hostname, | |
500 | + Printer.network_ip.is_(None) | |
501 | + ) | |
502 | + ).first() | |
503 | + | |
504 | + if counter is not None: | |
505 | + # Agora insere a impressora | |
506 | + log.info("Inserindo impressora com o IP %s", hostname) | |
507 | + session.execute( | |
508 | + Printer.__table__.insert().values( | |
509 | + network_ip=host.network_ip | |
510 | + ) | |
511 | + ) | |
512 | + session.flush() | |
513 | + continue | |
514 | + | |
460 | 515 | if isinstance(host, Printer): |
461 | 516 | # Vê se a impressora já está na base |
462 | 517 | results = session.query(Printer).filter(Printer.network_ip == hostname).first() |
... | ... | @@ -476,8 +531,21 @@ class ScanCommands(command.Command): |
476 | 531 | network_ip=hostname |
477 | 532 | ) |
478 | 533 | ) |
479 | - session.flush() | |
480 | 534 | log.info("Impressora %s adicionada novamente com sucesso", hostname) |
535 | + | |
536 | + # Agora atualiza informações do host | |
537 | + if host.mac_address is not None: | |
538 | + session.execute( | |
539 | + Host.__table__.update().values( | |
540 | + mac_address=host.mac_address, | |
541 | + name=host.name, | |
542 | + ports=host.ports | |
543 | + ).where( | |
544 | + Host.network_ip == hostname | |
545 | + ) | |
546 | + ) | |
547 | + session.flush() | |
548 | + log.info("Informações do host %s atualizadas com sucesso", hostname) | |
481 | 549 | else: |
482 | 550 | log.error("ERRO!!! Host não encontrado com o IP!!! %s", hostname) |
483 | 551 | else: |
... | ... | @@ -492,8 +560,34 @@ class ScanCommands(command.Command): |
492 | 560 | session.flush() |
493 | 561 | except IntegrityError, e: |
494 | 562 | log.error("Erro adicionando computador com o IP %s. IP Repetido\n%s", hostname, e.message) |
563 | + # Agora atualiza informações do host | |
564 | + if host.mac_address is not None: | |
565 | + session.execute( | |
566 | + Host.__table__.update().values( | |
567 | + mac_address=host.mac_address, | |
568 | + name=host.name, | |
569 | + ports=host.ports | |
570 | + ).where( | |
571 | + Host.network_ip == hostname | |
572 | + ) | |
573 | + ) | |
574 | + session.flush() | |
575 | + log.info("Informações do host %s atualizadas com sucesso", hostname) | |
495 | 576 | else: |
496 | 577 | log.info("Computador com o IP %s já cadastrado", hostname) |
578 | + # Agora atualiza informações do host | |
579 | + if host.mac_address is not None: | |
580 | + session.execute( | |
581 | + Host.__table__.update().values( | |
582 | + mac_address=host.mac_address, | |
583 | + name=host.name, | |
584 | + ports=host.ports | |
585 | + ).where( | |
586 | + Host.network_ip == hostname | |
587 | + ) | |
588 | + ) | |
589 | + session.flush() | |
590 | + log.info("Informações do host %s atualizadas com sucesso", hostname) | |
497 | 591 | else: |
498 | 592 | # Insere host genérico |
499 | 593 | results = session.query(Host).filter(Host.network_ip == hostname).first() |
... | ... | @@ -504,12 +598,42 @@ class ScanCommands(command.Command): |
504 | 598 | session.flush() |
505 | 599 | except IntegrityError, e: |
506 | 600 | log.error("Erro adicionando host genérico com o IP %s. IP Repetido\n%s", hostname, e.message) |
601 | + | |
602 | + # Agora atualiza informações do host | |
603 | + if host.mac_address is not None: | |
604 | + session.execute( | |
605 | + Host.__table__.update().values( | |
606 | + mac_address=host.mac_address, | |
607 | + name=host.name, | |
608 | + ports=host.ports | |
609 | + ).where( | |
610 | + Host.network_ip == hostname | |
611 | + ) | |
612 | + ) | |
613 | + session.flush() | |
614 | + log.info("Informações do host %s atualizadas com sucesso", hostname) | |
507 | 615 | else: |
508 | 616 | log.info("Host genérico com o IP %s já cadastrado", hostname) |
509 | 617 | |
618 | + # Agora atualiza informações do host | |
619 | + if host.mac_address is not None: | |
620 | + session.execute( | |
621 | + Host.__table__.update().values( | |
622 | + mac_address=host.mac_address, | |
623 | + name=host.name, | |
624 | + ports=host.ports | |
625 | + ).where( | |
626 | + Host.network_ip == hostname | |
627 | + ) | |
628 | + ) | |
629 | + session.flush() | |
630 | + log.info("Informações do host %s atualizadas com sucesso", hostname) | |
631 | + | |
510 | 632 | #session.flush() |
511 | 633 | session.close() |
512 | 634 | |
635 | + log.info("CARGA DO ARQUIVO DE REDE %s FINALIZADA!!!", network_file) | |
636 | + | |
513 | 637 | def import_printers(self): |
514 | 638 | """ |
515 | 639 | Importa impressoras já cadastradas e não presentes na base local |
... | ... | @@ -535,6 +659,86 @@ class ScanCommands(command.Command): |
535 | 659 | |
536 | 660 | session.close() |
537 | 661 | |
662 | + def get_mac(self): | |
663 | + """ | |
664 | + Atualiza MAC Address para o host selecionado | |
665 | + :return: | |
666 | + """ | |
667 | + if type(self.options.hosts) != list: | |
668 | + self.options.hosts = [self.options.hosts] | |
669 | + | |
670 | + session = self.cocar.Session | |
671 | + for host in self.options.hosts: | |
672 | + arp = ArpSession( | |
673 | + host=host, | |
674 | + iface=self.options.iface, | |
675 | + timeout=self.options.timeout | |
676 | + ) | |
677 | + | |
678 | + result = arp.scan() | |
679 | + | |
680 | + if result is not None: | |
681 | + log.debug("Atualizando MAC = %s para host = %s", result, host) | |
682 | + session.execute( | |
683 | + Host.__table__.update().values( | |
684 | + mac_address=result | |
685 | + ).where( | |
686 | + Host.network_ip == host | |
687 | + ) | |
688 | + ) | |
689 | + session.flush() | |
690 | + | |
691 | + session.close() | |
692 | + | |
693 | + def scan_mac(self): | |
694 | + """ | |
695 | + Scan all hosts to update macs | |
696 | + """ | |
697 | + processes = int(self.cocar.config.get('cocar', 'processes')) | |
698 | + # Create queues | |
699 | + task_queue = Queue() | |
700 | + done_queue = Queue() | |
701 | + | |
702 | + session = self.cocar.Session | |
703 | + results = session.query(Host).all() | |
704 | + for host in results: | |
705 | + arp = ArpSession( | |
706 | + host=host.network_ip, | |
707 | + iface=self.options.iface, | |
708 | + timeout=self.options.timeout | |
709 | + ) | |
710 | + task_queue.put(arp) | |
711 | + | |
712 | + #Start worker processes | |
713 | + for i in range(processes): | |
714 | + Process(target=worker_mac, args=(task_queue, done_queue)).start() | |
715 | + | |
716 | + # Get and print results | |
717 | + print 'Unordered results:' | |
718 | + for i in range(len(results)): | |
719 | + host_list = done_queue.get() | |
720 | + log.debug(host_list) | |
721 | + if host_list[1] is None: | |
722 | + log.error("Nao foi possivel encontrar o mac do host %s", host_list[0]) | |
723 | + continue | |
724 | + try: | |
725 | + log.debug("Atualizando MAC = %s para host = %s", host_list[1], host_list[0]) | |
726 | + session.execute( | |
727 | + Host.__table__.update().values( | |
728 | + mac_address=host_list[1] | |
729 | + ).where( | |
730 | + Host.network_ip == host_list[0] | |
731 | + ) | |
732 | + ) | |
733 | + session.flush() | |
734 | + except AttributeError, e: | |
735 | + log.error("Erro na atualização do MAC para host %s\n%s", host_list[0], e.message) | |
736 | + continue | |
737 | + | |
738 | + # Tell child processes to stop | |
739 | + for i in range(processes): | |
740 | + task_queue.put('STOP') | |
741 | + | |
538 | 742 | |
539 | 743 | def make_query(host): |
540 | 744 | """This does the actual snmp query |
... | ... | @@ -558,6 +762,17 @@ def make_query_printer(host): |
558 | 762 | return host.printer_dict() |
559 | 763 | |
560 | 764 | |
765 | +def make_query_mac(host): | |
766 | + """This does the actual snmp query | |
767 | + | |
768 | + This is a bit fancy as it accepts both instances | |
769 | + of SnmpSession and host/ip addresses. This | |
770 | + allows a user to customize mass queries with | |
771 | + subsets of different hostnames and community strings | |
772 | + """ | |
773 | + return host.scan_list() | |
774 | + | |
775 | + | |
561 | 776 | # Function run by worker processes |
562 | 777 | def worker(inp, output): |
563 | 778 | for func in iter(inp.get, 'STOP'): |
... | ... | @@ -569,4 +784,11 @@ def worker(inp, output): |
569 | 784 | def worker_printer(inp, output): |
570 | 785 | for func in iter(inp.get, 'STOP'): |
571 | 786 | result = make_query_printer(func) |
787 | + output.put(result) | |
788 | + | |
789 | + | |
790 | +# Function run by worker processes | |
791 | +def worker_mac(inp, output): | |
792 | + for func in iter(inp.get, 'STOP'): | |
793 | + result = make_query_mac(func) | |
572 | 794 | output.put(result) |
573 | 795 | \ No newline at end of file | ... | ... |
cocar/model/printer.py
... | ... | @@ -46,13 +46,30 @@ class Printer(Host): |
46 | 46 | """ |
47 | 47 | Exporta todos os contadores para a impressora |
48 | 48 | """ |
49 | - counter_list = session.query( | |
50 | - PrinterCounter | |
51 | - ).filter( | |
52 | - PrinterCounter.network_ip == self.network_ip | |
53 | - ).all() | |
54 | - | |
55 | - for counter in counter_list: | |
49 | + #query = session.query( | |
50 | + # PrinterCounter | |
51 | + #).filter( | |
52 | + # PrinterCounter.__table__.c.network_ip == self.network_ip | |
53 | + #) | |
54 | + | |
55 | + stm = """SELECT printer_counter.network_ip as ip_address, | |
56 | + host.mac_address, | |
57 | + host.inclusion_date, | |
58 | + host.scantime, | |
59 | + printer.model, | |
60 | + printer.serial, | |
61 | + printer.description, | |
62 | + printer_counter.counter, | |
63 | + printer_counter.counter_time | |
64 | +FROM host | |
65 | +JOIN printer ON host.network_ip = printer.network_ip | |
66 | +JOIN printer_counter ON printer.network_ip = printer_counter.network_ip | |
67 | +WHERE printer_counter.network_ip = '%s'""" % self.network_ip | |
68 | + | |
69 | + counter_list = session.execute(stm, mapper=PrinterCounter).fetchall() | |
70 | + | |
71 | + for elm in counter_list: | |
72 | + counter = PrinterCounter(**elm) | |
56 | 73 | print(counter) |
57 | 74 | result = counter.export_counter(server_url, session) |
58 | 75 | if result: |
... | ... | @@ -61,6 +78,7 @@ class Printer(Host): |
61 | 78 | log.error("Erro na remocao do contador %s para a impressora %s", counter.counter, self.network_ip) |
62 | 79 | return False |
63 | 80 | |
81 | + log.info("EXPORT DA IMPRESSORA %s FINALIZADO!!! %s CONTADORES EXPORTADOS!!!", self.network_ip, len(counter_list)) | |
64 | 82 | return True |
65 | 83 | |
66 | 84 | |
... | ... | @@ -157,7 +175,7 @@ class PrinterCounter(Printer): |
157 | 175 | return False |
158 | 176 | |
159 | 177 | if response.status_code == 200: |
160 | - log.info("Contador para a impressora %s com contador %s" | |
178 | + log.info("Contador para a impressora %s com contador %s " | |
161 | 179 | "exportado com sucesso", self.network_ip, self.counter) |
162 | 180 | # Remove o contador |
163 | 181 | session.execute( | ... | ... |
cocar/session.py
... | ... | @@ -7,6 +7,7 @@ import netsnmp |
7 | 7 | import subprocess |
8 | 8 | import logging |
9 | 9 | from . import Cocar |
10 | +import re | |
10 | 11 | |
11 | 12 | log = logging.getLogger() |
12 | 13 | |
... | ... | @@ -247,4 +248,80 @@ class NmapSession(object): |
247 | 248 | log.error("Install nmap: sudo apt-get install nmap") |
248 | 249 | return False |
249 | 250 | |
250 | - return True | |
251 | 251 | \ No newline at end of file |
252 | + return True | |
253 | + | |
254 | + | |
255 | +class ArpSession(object): | |
256 | + """ | |
257 | + Classe para buscar informações de MAC do ativo | |
258 | + """ | |
259 | + def __init__(self, | |
260 | + host, | |
261 | + iface='eth0', | |
262 | + timeout='10'): | |
263 | + """ | |
264 | + :param host: Endereço IP do host a ser escaneado | |
265 | + :param mac: MAC address do host | |
266 | + :param timeout: Timeout esperando pelo reply da interface | |
267 | + """ | |
268 | + self.host = host | |
269 | + self.iface = iface | |
270 | + self.timeout = timeout | |
271 | + | |
272 | + def scan(self): | |
273 | + """ | |
274 | + | |
275 | + :return: Somente MAc | |
276 | + """ | |
277 | + log.debug("Iniciando scan para o host %s", self.host) | |
278 | + try: | |
279 | + scanv = subprocess.Popen(["sudo", | |
280 | + "arping", | |
281 | + "-I", | |
282 | + self.iface, | |
283 | + "-c", | |
284 | + '1', | |
285 | + "-w", | |
286 | + self.timeout, | |
287 | + self.host], | |
288 | + stdout=subprocess.PIPE, | |
289 | + stderr=subprocess.PIPE).communicate()[0] | |
290 | + | |
291 | + match = re.search("(\[)(.*)(\])", scanv) | |
292 | + | |
293 | + if match: | |
294 | + return match.group(2) | |
295 | + | |
296 | + return match | |
297 | + except OSError: | |
298 | + log.error("Install arping: sudo apt-get install arping") | |
299 | + return None | |
300 | + | |
301 | + def scan_list(self): | |
302 | + """ | |
303 | + | |
304 | + :return: List com host e MAC | |
305 | + """ | |
306 | + log.debug("Iniciando scan para o host %s", self.host) | |
307 | + try: | |
308 | + scanv = subprocess.Popen(["sudo", | |
309 | + "arping", | |
310 | + "-I", | |
311 | + self.iface, | |
312 | + "-c", | |
313 | + '1', | |
314 | + "-w", | |
315 | + self.timeout, | |
316 | + self.host], | |
317 | + stdout=subprocess.PIPE, | |
318 | + stderr=subprocess.PIPE).communicate()[0] | |
319 | + | |
320 | + match = re.search("(\[)(.*)(\])", scanv) | |
321 | + | |
322 | + if match: | |
323 | + return [self.host, match.group(2)] | |
324 | + | |
325 | + return [self.host, match] | |
326 | + except OSError: | |
327 | + log.error("Install arping: sudo apt-get install arping") | |
328 | + return None | |
252 | 329 | \ No newline at end of file | ... | ... |
cocar/xml_utils.py
... | ... | @@ -32,10 +32,7 @@ class NmapXML(object): |
32 | 32 | if addr.get('addrtype') == 'ipv4': |
33 | 33 | host = addr.get('addr') |
34 | 34 | elif addr.get('addrtype') == 'mac': |
35 | - mac = { | |
36 | - 'address': addr.get('addr'), | |
37 | - 'vendor': addr.get('vendor') | |
38 | - } | |
35 | + mac = addr.get('addr') | |
39 | 36 | |
40 | 37 | # A chave do dicionário é o IP |
41 | 38 | self.hosts[host] = dict() |
... | ... | @@ -129,6 +126,25 @@ class NmapXML(object): |
129 | 126 | open_ports=host['ports'], |
130 | 127 | ) |
131 | 128 | return printer |
129 | + elif host.get('os'): | |
130 | + # Nesse caso já sei que é computador. Precisa identificar o OS | |
131 | + for os in host['os'].keys(): | |
132 | + if int(host['os'][os]['accuracy']) > accuracy: | |
133 | + accuracy = int(host['os'][os]['accuracy']) | |
134 | + os_final = os | |
135 | + | |
136 | + scantime = int(host.get('endtime')) - int(host.get('starttime')) | |
137 | + computer = model.computer.Computer( | |
138 | + ip_address=hostname, | |
139 | + mac_address=host.get('mac'), | |
140 | + hostname=host.get('hostname'), | |
141 | + inclusion_date=host.get('endtime'), | |
142 | + scantime=scantime, | |
143 | + open_ports=host.get('ports'), | |
144 | + so=host['os'][os_final] | |
145 | + ) | |
146 | + | |
147 | + return computer | |
132 | 148 | else: |
133 | 149 | # Desiste e retorna host genérico |
134 | 150 | host = model.host.Host( | ... | ... |