Problem mit Netzwerkprogrammierung

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
cruzer*
User
Beiträge: 21
Registriert: Mittwoch 4. September 2013, 10:50

Hallo,
schlage mich auch gerade mit meinen ersten Schritten in der Netzwerkprogrammierung mit Python.

Leider finde ich meinen fehler hier nicht?!

Code: Alles auswählen

import socket

def traceroute():
		#dest = raw_input('Bitte geben sie eine Adresse ein: ')
		#if dest == None:
		#		dest = 'google.de'
		# Variablen
		dest = 'google.de'
		destination = socket.gethostbyname(dest)
		port = 33434
		#verwendete Protokolle
		icmp = socket.getprotobyname('icmp')
		udp = socket.getprotobyname('udp')
		#Timetolive + maximale spruenge
		ttl = 1
		max_hops = 30
		while ttl < max_hops:
				# Socket zum empfangen
				receive_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
				# Socket fuer senden
				send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp)
				# ttl wird geaendert
				send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
				#bind socket + send
				receive_socket.bind(("",port))
				send_socket.sendto("",(destination, port))
				# erstelle leere Variablen
				current_addr = None
				current_name = None
				try:
						print "try 1 " + destination
						#ersten 512 bits des Empfangenen Objekts
						daten,current_addr = receive_socket.recvfrom(512)
						# ip adresse aus dem array
						current_addr = current_addr[0]
						try:
								print "try 2"
								current_name = socket.gethostbyaddr(current_addr)
						#fehler abfangen
						except socket.error:
								current_name = current_addr
				#fehler abfangen
				except socket.error:
						print "Fehler Socket"
						break
						
				finally:
						print "finally"
						#sockets schliessen
						send_socket.close()
						receive_socket.close()

				if current_addr is not None:
						current_host = "%s (%s)" %(current_name, current_addr)
				else:
						curr_host = "*"

				print "%d\t%s" %(ttl, current_host)

				#abbrechen falls noetig
				if current_addr == destination:
						break
				# ttl erhoehen
				ttl += 1
				
print traceroute()
Wenn ich diesen Code ausführe komme ich bis
"try 1 173.194.70.94"

Kann mir jemand weiterhelfen?

Edit: Ich verwende Python 2.7 und Sublime Text 3
Zuletzt geändert von Anonymous am Mittwoch 4. September 2013, 12:00, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Dein Code funktioniert.
Du solltest versuchen die Stelle zu identifizieren an der es nicht weiter geht. Wahrscheinlich diese:

Code: Alles auswählen

daten,current_addr = receive_socket.recvfrom(512)
, wo Daten ankommen sollten, es aber nicht tun.
Wie gesagt, hier funktioniert dein Beispiel problemlos.
BlackJack

@cruzer*: Ein Server muss nicht auf das „Ping” bei abgelaufener TTL antworten. Wenn er das nicht tut, wartest Du bis zum St. Nummerleins-Tag auf die Antwort. Du wirst also nach einer gewissen Zeit aufhören müssen auf eine Antwort zu warten. Dazu gibt es die `settimeout()`-Methode auf `socket`-Objekten.

Konvention ist vier Leerzeichen pro Einrückebene.

Den Rückgabewert von `traceroute()` mit ``print`` ausgeben macht keinen Sinn, da die Funktion keinen sinnvollen Rückgabewert liefert.

Die Namen `dest` und `destenation` nebeneinander zu haben ist verwirrend. Klar beschreiben beide das Ziel, aber man kann nicht erkennen wo der Unterschied in Wert und Bedeutung ist. `destenation` ist eine IP, das könnte man auch im Namen kenntlich machen. Und dann braucht man `dest` auch nicht mehr abkürzen, sondern könnte es `destenation` nennen. Würde sich auch prima als Argument für die Funktion machen. `max_hops` auch — mit einem Defaultwert.

In der ``while``-Schleife wird `ttl` von 1 bis `max_hops` hochgezählt — das ist eigentlich ein Job für eine ``for``-Schleife.

Die beiden Zuweisungen unter dem Kommentar ``# erstelle leere Variablen`` sind unnötig.

Das ``except`` auf Ebene des Schleifenkörpers ist keine angemessene Fehlerbehandlung. Und die gehört auch nicht in die Funktion. Man würde hier besser die Ausnahme die Funktion abbrechen lassen und die Fehlermeldung beim Aufrufer ausgeben lassen. Und dann auch mit der Ausnahme selbst und nicht einfach so ein generisches „da war ein Fehler” ausgeben, was dem Benutzer nicht wirklich hilft herauszufinden was die Ursache war.

Wenn man das ``except`` weg lässt, bleibt noch das ``finally`` — hier kann man mit `contextlib.closing()` und der ``with``-Anweisung arbeiten.

Kommentare sollten dem Leser einen Mehrwert liefern und nicht das offensichtliche aus dem Quelltext noch einmal wiederholen. Einige der eigentlich überflüssigen Kommmentare enthalten auch Fehler. Zum Beispiel ist `current_adr` kein „array” und es werden (maximal) 512 Bytes empfangen und nicht 512 Bits.

Das `current_addr` in der Schleife mal an ein Tupel mit IP und Portnummer und mal nur an eine IP gebunden wird ist verwirrend. Ähnliches gilt für `current_name`. Das mit den Tupel sollte man sich sparen und eine Umbenennung von `current_addr` zu `current_ip` sagt besser aus was der Wert bedeutet.

`curr_host` sollte wohl `current_host` heissen. Dieses ``if``/``else`` macht aber sowieso keinen Sinn, weil `current_ip` an der Stelle nicht `None` sein kann. Der „Sternchen-Fall” beim normalen ``traceroute``-Befehl ist wenn der Server bei dem das Paket wegen der TTL „gestorben” ist, gar nicht antwortet. In dem Fall hat man aber schon viel früher die Information das keine IP verfügbar ist, denn dann hat man für den `gethostbyaddr()` ja schon keine Argument und braucht diese Methode gar nicht erst aufrufen.

Eine Meldung wenn das Ziel innerhalb der `max_hops` Versuche nicht erreicht wurde, wäre nett. Da bietet sich ein ``else``-Zweig bei der Schleife an, da diese im Erfolgsfall mit ``break`` verlassen wird.

Muss man die Sockets in der Schleife eigentlich ständig neu erstellen? Bei funktioniert es auch wenn ich die beiden Sockets nur einmal vor der Schleife erstelle.

Das könnte dann insgesamt so aussehen:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
import socket
from contextlib import closing


def traceroute(destination, max_hops=30):
    destination_ip = socket.gethostbyname(destination)
    port = 33434
    icmp = socket.getprotobyname('icmp')
    udp = socket.getprotobyname('udp')
    with closing(
        socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp)
    ) as send_socket:
        with (
            closing(socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp))
        ) as receive_socket:
            receive_socket.bind(('', port))
            receive_socket.settimeout(3)
            for ttl in xrange(1, max_hops + 1):
                send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
                send_socket.sendto('', (destination_ip, port))
                try:
                    _data, (current_ip, _port) = receive_socket.recvfrom(512)
                except socket.timeout:
                    current_ip = None

                if current_ip:
                    try:
                        current_name, _, _ = socket.gethostbyaddr(current_ip)
                    except socket.error:
                        current_name = current_ip
                    current_host = '{0} ({1})'.format(current_name, current_ip)
                else:
                    current_host = '*'

                print '{0:d}\t{1}'.format(ttl, current_host)

                if current_ip == destination_ip:
                    break
            else:
                print 'Ziel nicht in {0} hops erreicht.'.format(max_hops)


def main():
    try:
        traceroute('python-forum.de')
    except socket.error as error:
        print error


if __name__ == '__main__':
    main()
Man könnte jetzt noch das Verfolgen der Route von der Ausgabe trennen, in dem man Beispielsweise eine Generatorfunktion aus `traceroute()` macht, welche die einzelnen IPs auf der Strecke liefert.
cruzer*
User
Beiträge: 21
Registriert: Mittwoch 4. September 2013, 10:50

wow! Vielen Dank für die ausführliche Antwort!
Ich werde versuchen es umzusetzen und mich wieder melden.

@sparrow
ja sie hängt an der Stelle.
cruzer*
User
Beiträge: 21
Registriert: Mittwoch 4. September 2013, 10:50

So da bin ich wieder. Egal wie ich es drehe und wende, der Code funktioniert so nicht. Ich bekomme immer ein Timeout am Readsocket.

Kann es sein, dass ich zum lesen des Sockets den selben Port benötige, auf dem ich auch Sende?
Zum Senden benötige ich einen Datagram Socket und zum empfangen benötige ich einen RAW-Socket.
Muss ich also meinen Sendsocket an einen Port binden bevor ich das UDP Paket lossende?
BlackJack

@cruzer*: Bei mir funktioniert mein Quelltext und bei sparrow funktioniert sogar Dein Quelltext — zumindest solange in der Kette kein Host ist der wirklich nicht antwortet. Kann es sein dass in *Deinem* Netzwerk irgendwo eine Firewall sitzt, die solche Pakete blockt?
cruzer*
User
Beiträge: 21
Registriert: Mittwoch 4. September 2013, 10:50

Also mit Wireshark sehe ich die ICMP Pakete.

mit einem verändertem Code bekomme ich mittlerweile Antworten. Jedoch ist current_ip immer meine Netzwerkeigene also 192.168.1.3

Code: Alles auswählen

#!/usr/bin/python


import socket
import time
from contextlib import closing

# variables
ttl = 1
BUF_MAX = 4096
DEST_PORT = 53
WAIT_TIME = 0.0005
MAX_HOPS = 20
ORIGIN_PORT = 2112 #starts with 1024
port_counter = 1
QUERY_DNS = '\xde\xa3\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x08facebook\x03com\x00\x00\x01\x00\x01'

def sendquery(dest_ip):
	for ttl in xrange(1,MAX_HOPS+1):
		with (
        	closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM))
        ) as send_socket:
        	#send_socket.setblocking(0)
			#send_socket.bind(('', ORIGIN_PORT))
			send_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
			try:
				send_socket.sendto(QUERY_DNS, (dest_ip, DEST_PORT))
			except socket.error as e:
				print "error in sendto"
			time.sleep(2)
		with (
			closing(socket.socket(socket.AF_INET, socket.SOCK_RAW))
		) as read_socket:
			read_socket.bind(('', ORIGIN_PORT))
			read_socket.settimeout(60)
			try:
				data, (current_ip, _port) = read_socket.recvfrom(BUF_MAX)
				print str(ttl) + " received data from : " + current_ip
				try:
					current_name, _, _ = socket.gethostbyaddr(current_ip)
					print "name = " + current_name
				except socket.error:
					print "error current_name "
			except socket.timeout:
				print "Socket timeout"
                current_ip = None
        if current_ip == dest_ip:
        	break


def main():
	try:
		sendquery("8.8.8.8")
	except socket.error:
		print "main error"

if __name__ == "__main__":
	main()



Aber wieso ist das immer meine eigene ip?
Zuletzt geändert von cruzer* am Donnerstag 24. Oktober 2013, 12:01, insgesamt 1-mal geändert.
cruzer*
User
Beiträge: 21
Registriert: Mittwoch 4. September 2013, 10:50

Fehler hier war vorallem das fehlende:

Code: Alles auswählen

# receive all packages
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
Antworten