Timeout setzen?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Hallo Leute,

ich beschäftige mich leider noch nicht all zu lange mit Python. In meinem Script pinge ich verschiedene Hosts an, und falls verfügbar, wird noch mal eine SNMP-Abfrage gemacht. Leider dauert beides beides ziemlich lange, falls es nicht verfügbar ist. Meine Frage also: wie kann ich einen Befehl mit Timeout bearbeiten?

Beispielsweise die folgende Abfrage

Code: Alles auswählen

import os

if os.system("ping -c1 127.0.0.1")==0:
      print("erreichbar")
else:
      print("nicht erreichbar")
Ich weiß, dass es bestimmte Parameter (z.B. -W) für den Ping-Befehl gibt. Jedoch könnte ich diese nicht auf die SNMP-Abfrage anwenden.

Lieben Gruß
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Dann zeig doch mal deine SNMP-Anfrage.
BlackJack

@chpo7234: Anstelle von `os.system()` sollte man übrigens besser das `subprocess`-Modul verwenden, zum Beispiel `call()`:

Code: Alles auswählen

ip_address = '127.0.0.1'
if subprocess.call(['ping', '-c1', ip_address]) == 0:
    # ...
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Danke für deinen Rat, BlackJack!

Mein Script sieht bis jetzt wie folgt aus:

Code: Alles auswählen

from pysnmp.entity.rfc3413.oneliner import cmdgen                                       #SNMP-Modul
import sys                                                                              #sys: fuer die Parameter-Uebergabe
import os                                                                               #os: fuer das anpingen
import socket                                                                           #socket: rDNS
 
ip = sys.argv[1]
abstand = " ==> "
 
if os.system("ping -c1 -W1 " + ip) == 0:
 
        cmdGen = cmdgen.CommandGenerator()
 
        rDNS = ip + abstand + socket.gethostbyaddr(ip)[0]
 
        errorIndication, errorStatus, errorIndex, varBinding = cmdGen.getCmd(
                cmdgen.CommunityData("public"),
                cmdgen.UdpTransportTarget((ip, 161)),
                "1.3.6.1.2.1.1.1.0"
        )
 
        if errorIndication or errorStatus:
                print(rDNS)
        else:
                print(rDNS + abstand + varBinding[0][1])
else:
        print(ip + abstand + "ist <b>nicht verfuegbar</b>!<br>")
subprocess werde ich hier demnach noch anpassen.

Gruß
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Versuch:

Code: Alles auswählen

        errorIndication, errorStatus, errorIndex, varBinding = cmdGen.getCmd(
                cmdgen.CommunityData("public"),
                cmdgen.UdpTransportTarget((ip, 161), timeout=1.5, retries=0),
                "1.3.6.1.2.1.1.1.0"
        )
API gibt es hier.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Moin Problem ist leider, dass ich letztendlich nicht nur eine IP, sondern einen ganzen Adressbereich scannen will. Wenn denn mal 200 oder mehr IP's gescannt werden, möchte ich vermeiden, dass der SNMP- oder Ping-Versuch über eine Sekunde hinaus geht - da die Wartezeit sonst immens steigt. Lese mir zur Zeit ein paar Artikel über signal und setitimer durch. Vielleicht finde ich ja noch einen annehmbaren Code. Falls wer Ergänzungen oder Lösungsvorschläge hat, wäre ich erfreut!

Lieben Gruß
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Und was ist, wenn die Gegenstelle wirklich länger als eine Sekunde braucht?
Beschäftige dich Threads und einem Threadpool.
Während ein Thread darauf wartet, dass eine Antwort kommt, können ja parallel bereits andere IPs angefragt werden.
Dann kannst du den Timeout unangetastet lassen.
200 IPs parallel zu pingen sollte kein Problem sein.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Danke für den Rat!

Momentan sieht die Situation so aus, dass eine PHP-Seite den erforderten Adressbereich mittels Schleife durchläuft, und dann jeweils immer das Python-Script aufruft. Wäre das so denn noch zu realisieren? Oder müsste ich den Adressbereich dann eher direkt an Python übergeben, so dass dieser den Bereich parallel abarbeiten kann?
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

sparrow hat geschrieben:Und was ist, wenn die Gegenstelle wirklich länger als eine Sekunde braucht?
Da sich ein hartnäckiges Gerücht hält, auf Pings nicht zu antworten hätte irgendwelche Vorteile für die Sicherheit, konfigurieren viele Admins Systeme entsprechend. Wenn man Addressbereiche scannt wird man zwangsläufig auf einige solche Systeme stoßen was den Scan mit üblichen 30s Timeouts ins unbrauchbar langsam macht. Eine Timeout von einer Sekunde ist vielleicht etwas kurz aber auch nicht vollkommen absurd wenn man bereit ist false negatives zu haben.
BlackJack

@chpo7234: Ja man würde dann eher den Addressbereich an das Python-Programm übergeben.

Wegen Threadpool: `concurrent.futures` könnte man sich da anschauen wenn man sich nicht selbst einen Pool basteln möchte.

Ansonsten kann man auch das Programm `fping` anstelle von `ping` ins Auge fassen.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Moin moin,

ich bin es noch mal.. Der Tipp mit dem Modul "concurrent.futures" war soweit echt super, da bin ich dir echt dankbar, BlackJack! Ich habe alles soweit umgesetzt und über die Shell werden die Abfragen asynchron abgefragt. Super! Problem ist nun nur leider, sobald ich in eines meiner Python-Scripte das Modul "concurrent.futures" importiere, möchte php das Script nicht mehr ausführen und überspringt es scheinbar. Das ist echt schade, nach der ganzen Arbeit.

Habt ihr einen Tipp, wie ich das beheben kann oder könnte jemand eine Annahme treffen, wieso das so ist?

Andernfalls versuche ich nun meinen eigenen Threadpool(http://www.python-kurs.eu/threads.php) zu erstellen, abseits des Moduls "concurrent.futures", und hoffe so, dass PHP das auf diesem Wege mag...
Lieben Gruß
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@chpo7234: Meldet Python denn einen Fehler? Welche Python-Version benutzt Du? concurrent.futures gibt es erst ab Python 3.2. Für Python 2 gibt es einen Backport, den Du extra installieren mußt.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Nein, Python meldet beim Ausführen in der Shell keine Fehler. Alles wird so gemacht, wie ich es möchte. Sobald ich das Script mit PHP über Python3 aufrufe, wird es übersprungen. Und das macht PHP bei jedem Python-Script, sobald ich das futures-Modul einbinde.
Ich bastel mir jetzt etwas mit dem Modul threading, damit klappt es scheinbar mit PHP. :)
BlackJack

@chpo7234: Wenn es mit `threading` klappt, dann sollte es auch mit `concurrent.futures` klappen wenn man dort den `ThreadPoolExecutor` verwendet. Der benutzt ja letztendlich auch `threading`.

Das Programm wird auch sicher nicht von PHP übersprungen sondern es wird ausgeführt und das funktioniert dann aus irgendwelchen Gründen nicht, die man dann doch aber in den Protokolldateien vom Webserver nachlesen kann.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Achja, Log-Dateien...

Folgender Eintrag ließ sich auf den Fehler zurückführen:
File "/opt//omd/sites/usrname/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/patch.py", line 31, in <module>
from __builtin__ import property as bltin_property
ImportError: No module named '__builtin__'
BlackJack

@chpo7234: Ähm das ist fast unmöglich weil das Modul im Python (2) Interpreter fest eingebaut ist. Da kann es also nicht einmal Probleme geben mit Pfaden oder Dateien. Kann es sein dass Du einem Python 3 irgendwie versuchst die Pfade zu einer Python 2 Installation als Modulsuchpfad unterzujubeln‽ Ebenfalls komisch ist das `multiprocessing` ein EGG ist, denn das ist eigentlich Bestandteil der Standardbibliothek. Wo kommt das her?
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Sorry, hier noch mal der vollständige Fehlerlog:
Traceback (most recent call last):
File "tpe.py", line 7, in <module>
import concurrent.futures #multithread => Fuehrt PHP nicht aus!
File "/usr/lib/python3.4/concurrent/futures/__init__.py", line 17, in <module>
from concurrent.futures.process import ProcessPoolExecutor
File "/usr/lib/python3.4/concurrent/futures/process.py", line 53, in <module>
import multiprocessing
File "/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/__init__.py", line 64, in <module>
import multiprocessing.patch
File "/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg/multiprocessing/patch.py", line 31, in <module>
from __builtin__ import property as bltin_property
ImportError: No module named '__builtin__'
Wenn ich das future-Modul in irgendein Python-Script einbinde, denn lässt es sich über die Bash NUR über python3 öffnen. Über PHP funktioniert es über den Befehl "python3" nicht: denn kommt halt der Error-Log. Ich habe soweit eigentlich keine Pfade zugewiesen. Im php führe ich die Datei wie folgt aus: exec("python3 Dateiname parameter1")
BlackJack

@chpo7234: Für Python 2 muss man das `concurrent.futures`-Modul nachträglich installieren, bei Python 3 ist es bereits in der Standardbibliothek.

Deine Python (3) Installation oder die Konfiguration Deines Systems ist kaputt, denn das hier

/omd/sites/USRNAME/lib/python/multiprocessing-2.6.2.1-py2.7-linux-x86_64.egg

sollte nicht existieren. Das ist ganz klar ein Modul/Package für Python 2.7 und hat im Modulsuchpfad von Python 3 nichts zu suchen.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Danke, werde ich mich noch mal mit beschäftigen!

Bis jetzt habe ich Code aus folgendem Artikel abgeleitet: http://www.python-kurs.eu/threads.php (letztes Beispiel)

Irgendwie habe ich jetzt aber nicht das Gefühl, dass die Threads asynchron abgearbeitet werden. Vor allem, da sie mir auch, wie zuvor eingelesen, wieder ausgegeben werden. Das heißt: Ausgabe beginnt mit erster IP und endet mit letzter. Hat hier sonst jemand eine andere Url oder einen anderen Vorschlag für mich?

60 Sekunden für 500 Pings und SNMP-Abfragen im Multithread halte ich irgendwie für zu lange..

Ich bin ein wirklicher Python-Anfänger und mir ist es auch ein wenig unangenehm, aber ich poste trotzdem mal mein Code.

Code: Alles auswählen

import os						# ping
import re						# regular expression
import threading					# Multithreading
import sys						# Parameter-UEbergabe
import socket						# rDNS
import subprocess
import ipconvert as conv
from pysnmp.entity.rfc3413.oneliner import cmdgen	# SNMP

wordwrap = "<br>"
space = " => "

###################################################################################
##
##	Methode: Check SNMP- und rDNS-Status
##
###################################################################################
def info_rDNS_SNMP(ip):
	try:
		rDNS = socket.gethostbyaddr(ip)[0]
	except:
		rDNS = " "

	cmdGen = cmdgen.CommandGenerator()
	errorIndication, errorStatus, errorIndex, varBinding = cmdGen.getCmd(
		cmdgen.CommunityData("public"),
		cmdgen.UdpTransportTarget((ip, 161), timeout=1, retries=0),
		"1.3.6.1.2.1.1.1.0"
	)

	if errorIndication or errorStatus:
		return rDNS
	else:
		return rDNS + space + varBinding[0][1]

#####################################################################################
##
##	Klasse: IP-Check
##
#####################################################################################
class ip_check(threading.Thread):

############################
## Initialisierung
############################
	def __init__ (self,ip):
		threading.Thread.__init__(self)
		self.ip = ip
		self.__successful_pings = -1

############################
## Multithread
############################
	def run(self):
		ping_out = os.popen("ping -q -c2 -W1 "+self.ip,"r")
		while True:
			line = ping_out.readline()
			if not line: break
			n_received = re.findall(received_packages,line)
			if n_received:
				self.__successful_pings = int(n_received[0])

############################
## Ping-Ergebnis
############################
	def status(self):
		if self.__successful_pings == 0:
			return "no response"
		elif self.__successful_pings == 1:
			return "alive, but 50 % package loss"
		elif self.__successful_pings == 2:		#Host erreichbar
			return info_rDNS_SNMP(self.ip)
		else:
			return "shouldn't occur"

######################################################################################
##
##	Pruefe IP's
##
######################################################################################
received_packages = re.compile(r"(\d) received")

check_results = []
results = []
string_results = ""

###############################
## Checke IP-Addressbereich
###############################
start = int(conv.ip2long(sys.argv[1]))
end = int(conv.ip2long(sys.argv[2]))

for lv in range(start,end+1):
	ip = conv.long2ip(lv)
	current = ip_check(ip)
	check_results.append(current)
	current.start()

################################
## Ergebnisse in Liste speichern
################################
for el in check_results:
	el.join()
	results.append(string_results + el.ip + " : " + el.status())
#results.sort()
#for entry in results:
#	print(entry)
Lieben Gruß
BlackJack

@chpo7234: Doch die werden (teilweise) asynchron abgearbeitet. Die Reihenfolge der Ausgabe kommt dadurch zustande das Du die Ergebnisse von den Thread-Exemplaren in der Reihenfolge abfragst in der sie erstellt und gestartet wurden. Da muss dann die Ausgabe eventuell auf ein Ergebnis warten, aber an der Gesamtlaufzeit ändert das nichts. Allerdings werden SNMP-Abfragen alle nacheinander im Hauptthread ausgeführt statt parallel, denn der Code wird ja erst ausgeführt wenn `status()` aufgerufen wird.

Anmerkungen zum Quelltext: Formatierung und Namensschreibweisen entsprechen teilweise nicht dem Style Guide for Python Code.

Diese umrahmten Kommentare zerstören IMHO den Lesefluss ungemein. Der Inhalt ist zum Teil auch total überflüssig. Man muss vor einer Klasse nicht kommentieren dass das eine Klasse ist oder vor einer `__init__()` dass dort initialisiert wird.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer `main()`-Funktion.

Keine nackten ``except:``\s verwenden. Da wird *alles* behandelt, auch Sachen mit denen Du gar nicht rechnest und für die die Behandlung unpassend ist.

Zwei führende Unterstriche bei Attributen sind dazu da um Namenskollisionen bei Mehrfachvererbung oder tiefen Typhierharchien zu vermeiden. Beides Sachen die weder hier noch allgemein in Python oft gemacht werden. Implementierungsdetails werden mit *einem* führenden Unterstrich gekennzeichnet.

Als ”(noch) nichts”-Wert sollte man keinen speziellen Wert nehmen der vom Typ her auch mit einem tatsächlichen Wert passen würde. In Python gibt es den Wert `None` für so etwas. Ausserdem ist hier ein Fehler. Die Anzahl wird bei Dir mit -1 initialisiert, aber wenn von ``ping`` keine Ausgabe kommt auf die der reguläre Ausdruck zutrifft, dann bleibt der Wert bei -1 und führt dann bei `status()` zu einem Rückgabewert der eher eine Ausnahme sein sollte.

`re.findall()` macht keinen Sinn wenn man nur den ersten Treffer überhaupt verarbeitet.

Namen sollte man erst definieren wenn man sie benötigt und nicht auf Vorrat irgendwo am Anfang einer Funktion. Das ist unübersichtlich und bei Programmänderungen vergisst man gerne mal solche Definitionen und sammelt so Müll an.

Man sollte keine Abkürzungen verwenden die nicht allgemein bekannt sind. Ich rätsele immer noch was `lv` bedeuten sollte. Auch `el` ist nicht schön.

`string_results` hat keinen Effekt. Eine leere Zeichenkette kann man so oft ”addieren” wie man lustig ist, das ändert nichts am Ergebnis. Der Name lässt auch vermuten, dass das eigentlich etwas anderes sein/werden sollte.

Wenn das Ergebnis an ein anderes Programm übermittelt werden soll, würde ich keinen Text verwenden und auch nicht krude HTML-Teile ausgeben sondern ein verbreitetes Serialisierungsformat verwenden wie beispielsweise JSON.

Ich lande dann bei so etwas (ungetestet):

Code: Alles auswählen

import json
import re
import socket
import sys
from collections import OrderedDict
from subprocess import check_output
from threading import Thread

import ipconvert
from pysnmp.entity.rfc3413.oneliner import cmdgen
 
ARROW = '=>'


def check_snmp(ip):
    try:
        reverse_dns = socket.gethostbyaddr(ip)[0]
    except socket.error:
        reverse_dns = ' '

    command_generator = cmdgen.CommandGenerator()
    error_indication, error_status, _, var_binding = command_generator.getCmd(
        cmdgen.CommunityData('public'),
        cmdgen.UdpTransportTarget((ip, 161), timeout=1, retries=0),
        '1.3.6.1.2.1.1.1.0'
    )

    if error_indication or error_status:
        return reverse_dns
    else:
        return '{0} {1} {2}'.format(reverse_dns, ARROW, var_binding[0][1])


class IpCheck(Thread):
    
    RECEIVED_PACKAGES_RE = re.compile(r'(\d+) received')

    def __init__(self, ip):
        Thread.__init__(self)
        self.ip = ip
        self.result = None

    def run(self):
        match = self.RECEIVED_PACKAGES_RE.search(
            check_output(['ping', '-q', '-c2', '-W1', self.ip])
        )
        successful_ping_count = int(match.group(1)) if match else 0

        if successful_ping_count == 0:
            self.result = 'no response'
        elif successful_ping_count == 1:
            self.result = 'alive, but 50% package loss'
        elif successful_ping_count == 2:
            self.result = check_snmp(self.ip)
        else:
            assert False


def ip_range(start, end):
    return (
        ipconvert.long2ip(i)
        for i in range(ipconvert.ip2long(start), ipconvert.ip2long(end) + 1)
    )


def main():
    start = sys.argv[1]
    end = sys.argv[2]

    ip_checks = []
    for ip in ip_range(start, end):
        ip_check = IpCheck(ip)
        ip_check.start()
        ip_checks.append(ip_check)

    result = OrderedDict()
    for ip_check in ip_checks:
        ip_check.join()
        result[ip_check.ip] = ip_check.result

    print(json.dumps(result))


if __name__ == '__main__':
    main()
Antworten