SocketServer an Port oberhalb von 1024 binden (Ubuntu 7.04)

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Warhog
User
Beiträge: 11
Registriert: Samstag 26. Januar 2008, 21:53
Kontaktdaten:

Hi,

ich bastel gerade an einem kleinen XMLRPC-Script und benutze dafür den SimpleXMLRPCServer. Soweit, so gut, lokal funktioniert alles. Als ich dann gerade von einem anderen Rechner 'nen RPC abschickte konnte ich nicht zu dem Server connecten.

Langer Rede kurzer Sinn, nach einigem Testen auch mit den einfachsten Sockets "wo gibt" habe ich jetzt herausgefunden, dass das anbinden an einen Port kleiner 1024 funktioniert - wenn ich root bin, logischerweise. Darüber nicht. Auch nicht als root. Was funktioniert ist, dass ich lokal an diesen port verbinden kann, nicht aber von einem anderen Rechner (im selben Netzwerk).

Ich hab schon diverse HOWTOs etc. gegoogled und nirgendwo eine Antwort gefunden, kann es sein, dass das irgendeine verquere Einstellung an meinem Betriebssystem ist oder liegt's an Python?

Hier zur Verdeutlichung nochmal was das Problem ist:

Port unterhalb 1024
192.168.2.23, als root

Code: Alles auswählen

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("192.168.2.23", 84)) # funktioniert
192.168.2.17

Code: Alles auswählen

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.2.23", 84)) # funktioniert
Port überhalb
192.168.2.23, egal ob als root oder nicht

Code: Alles auswählen

# socket importieren und aufsetzen
s.bind(("192.168.2.23", 8117)) # kein fehler
192.168.2.23 (Selber Host)

Code: Alles auswählen

# socket importieren und aufsetzen
s.connect(("192.168.2.23", 8117)) # kein fehler, tut
192.168.2.17

Code: Alles auswählen

# ...
s.connect(("192.168.2.23", 8117))
# fehler, connection refused
Ich versteh das nicht. Das bizarre ist ja vor allen Dingen, dass man auch als root nicht an nen höheren Port binden kann - obwohl über 1024 doch unkritische liegen??

ich hab absolut keine Idee mehr.

Gruß, Warhog

EDIT: In den Beispielen fehlt das anschließende s.listen(5), ich habs in den Scripten drin, daran liegt's also nicht.
Zuletzt geändert von Warhog am Sonntag 27. Januar 2008, 00:23, insgesamt 1-mal geändert.
BlackJack

Den anderen Rechner interessiert das herzlich wenig ob jemand der sich mit ihm verbinden möchte bei sich lokal Root-Rechte hat. Hast Du irgendeine Firewall laufen?
Warhog
User
Beiträge: 11
Registriert: Samstag 26. Januar 2008, 21:53
Kontaktdaten:

Das ist mir wohl klar, dass den anderen Rechner das nicht interessiert, aber den Linux-Kernel interessiert das, denn 'nen Port unter 1024 darf ein normaler User nicht binden.

Keine Firewall, Standard-Installation von Ubuntu 7.04 Feisty Fawn. Auch nicht an den iptables rumgepfuscht!
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

`s.listen(..)' vergessen?
Warhog
User
Beiträge: 11
Registriert: Samstag 26. Januar 2008, 21:53
Kontaktdaten:

argh, ja, oben in den Beispieln, ja. Im Script steht's drin. (Würd ja auch lokal gar nicht laufen wenn ich's nicht drin hätte)

Die Sockets sind schon korrekt aufgesetzt, es ist wirklich nur ein Problem mit den Ports. Denn: Ein Port unterhalb der 1024-Grenze, wie eben z.B: 84 läuft - auch mit dem anderen Rechner. Der selbe Code nur mit verändertem Port ( 8117, 50000, schlag-mich-tot ) funktioniert nur lokal. Und ich hab absolut keine Ahnung warum das so ist.

Doof ist eben nur, dass das Binden an einen port unterhalb von 1024 eben wie erwähnt Root-Rechte benötgt, sonst hätte ich im Zweifelsfall gar kein Problem damit ganz frisch und frech irgendeinen Port zu klauen.

Ich wälze bereits seit 2 Stunden Webseiten um zu schauen ob's mit Ubuntu zusammenhängt, aber Fehlanzeige.

Das einzige was mich ein wenig an mein Problem brachte war ein Abschnitt in einem HOWTO: http://docs.python.org/dev/howto/socket ... g-a-socket
A couple things to notice: we used socket.gethostname() so that the socket would be visible to the outside world. If we had used s.bind(('', 80)) or s.bind(('localhost', 80)) or s.bind(('127.0.0.1', 80)) we would still have a “server” socket, but one that was only visible within the same machine.
Ich hab's auch bereits mit dem gethostname() versucht, ändert aber an dem Problem absolut gar nichts. Und wie erwähnt: Mit nem Port unterhalb von 1024 geht's ja.

Ratlos.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Schon mal versucht an 0.0.0.0 zu binden?
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Warhog
User
Beiträge: 11
Registriert: Samstag 26. Januar 2008, 21:53
Kontaktdaten:

japp.

hier was mir

Code: Alles auswählen

sudo netstat -tulpen | grep python
ausgibt:

Code: Alles auswählen

tcp        0      0 192.168.2.23:1025       0.0.0.0:*               LISTEN     1000       236278     24268/python
tcp        0      0 192.168.2.23:8117       0.0.0.0:*               LISTEN     1000       222100     24268/python
tcp        0      0 0.0.0.0:8119            0.0.0.0:*               LISTEN     1000       254259     24268/python
Die Ports tun alle nicht. Nur lokal eben. Binde ich nen Port unterhalb 1024 als Root sehen die obigen Zeilen nicht anders aus, nur dass es ben tut.

(EDIT: Erklärung: hab die Ports 1025, 8117 und 8119 mal gebunden, 8119 an 0.0.0.0, die anderen an 192.168.2.23)
Warhog
User
Beiträge: 11
Registriert: Samstag 26. Januar 2008, 21:53
Kontaktdaten:

:(

Hab jetzt nochmal folgendes Beispielskript geschrieben:

Code: Alles auswählen

import SimpleXMLRPCServer
import threading

s = SimpleXMLRPCServer.SimpleXMLRPCServer

s1 = s(("192.168.2.23", 8117))
s2 = s(("127.0.0.1", 8118))
s3 = s(("0.0.0.0", 8119))
s4 = s(("", 8120))
s5 = s(("localhost", 8121))
s6 = s(("industrial", 8122))

try:
	sA = s(("192.168.2.23", 81))
	sB = s(("127.0.0.1", 82))
	sC = s(("0.0.0.0", 83))
	sD = s(("", 84))
	sE = s(("localhost", 85))
	sF = s(("industrial", 86))
except:
	pass
finally:
	pass

class threadedServer(threading.Thread):
	def __init__(self, obj):
		threading.Thread.__init__(self)
		self.server = obj

	def run(self):
		self.server.serve_forever()

threadedServer(s1).start()
threadedServer(s2).start()
threadedServer(s3).start()
threadedServer(s4).start()
threadedServer(s5).start()
threadedServer(s6).start()

threadedServer(sA).start()
threadedServer(sB).start()
threadedServer(sC).start()
threadedServer(sD).start()
threadedServer(sE).start()
threadedServer(sF).start()
Wenn ich es als root ausführe werden die Ports 8117 bis 8122 und 81 bis 86 gebunden, allerdings sind nur die Ports 82 bis 85 sichtbar für die Außenwelt.

Wenn ich's nicht als root ausführe werden die Ports 8117 bis 8122 gebunden, wie zuvor ist allerdings keiner sichtbar für die Außenwelt.

netstat -tulpen gibt folgendes aus (ich kann absolut keinen Unterschied zwischen den Ports feststellen die der Außenwelt zugänglich sind und denen die's nicht sind)

Code: Alles auswählen

tcp        0      0 192.168.2.23:81         0.0.0.0:*               LISTEN     0          32246      13592/python
tcp        0      0 127.0.0.1:82            0.0.0.0:*               LISTEN     0          32247      13592/python
tcp        0      0 0.0.0.0:83              0.0.0.0:*               LISTEN     0          32248      13592/python
tcp        0      0 0.0.0.0:84              0.0.0.0:*               LISTEN     0          32249      13592/python
tcp        0      0 127.0.0.1:85            0.0.0.0:*               LISTEN     0          32250      13592/python
tcp        0      0 192.168.2.23:8117       0.0.0.0:*               LISTEN     0          32238      13592/python
tcp        0      0 127.0.1.1:86            0.0.0.0:*               LISTEN     0          32251      13592/python
tcp        0      0 127.0.0.1:8118          0.0.0.0:*               LISTEN     0          32239      13592/python
tcp        0      0 0.0.0.0:8119            0.0.0.0:*               LISTEN     0          32240      13592/python
tcp        0      0 0.0.0.0:8120            0.0.0.0:*               LISTEN     0          32241      13592/python
tcp        0      0 127.0.0.1:8121          0.0.0.0:*               LISTEN     0          32242      13592/python
tcp        0      0 127.0.1.1:8122          0.0.0.0:*               LISTEN     0          32245      13592/python
Ich werd's jetzt nochmal auf anderen Maschinen/Setups versuchen, u.a. Windows (grmpf) und dann mal weiterschauen um herauszufinden ob's ein Ubuntu- oder Linux-spezifisches Problem ist oder an Python liegt.

gruß, und danke allen die sich damit beschäftigt haben.
lunar

Warhog hat geschrieben:Wenn ich es als root ausführe werden die Ports 8117 bis 8122 und 81 bis 86 gebunden, allerdings sind nur die Ports 82 bis 85 sichtbar für die Außenwelt.
Port 85 ist sichtbar für die Außenwelt? Port 85 ist nur lokal an 127.0.0.1 gebunden. Wenn der tatsächlich öffentlich sichtbar ist, ist die Netzwerkkonfiguration ziemlich kaputt.
Wenn ich's nicht als root ausführe werden die Ports 8117 bis 8122 gebunden, wie zuvor ist allerdings keiner sichtbar für die Außenwelt.
Die Ausgabe von netstat sagt was anderes. Hast du deinen "Server" mal gescannt? Mindestens 192.168.2.23:8117 muss öffentlich verfügbar sein.

In deiner netstat Ausgabe fallen übrigens einige Merkwürdigkeiten auf:

Code: Alles auswählen

tcp        0      0 127.0.1.1:86            0.0.0.0:*               LISTEN     0          32251      13592/python
127.0.1.1 ist die Adresse, auf die der Hostname "industrial" aufgelöst wird? Das ist mindestens merkwürdig. Was steht den in deiner /etc/hosts?

Btw, bist du sicher, dass der Client, den du zum Testen verwendet, eine korrekte Netzwerkkonfiguration hat?
Warhog
User
Beiträge: 11
Registriert: Samstag 26. Januar 2008, 21:53
Kontaktdaten:

Code: Alles auswählen

$ cat /etc/hosts
> 127.0.0.1 localhost
> 127.0.1.1 industrial
> 
> # The following lines are desirable for IPv6 capable hosts
> ::1 ip6-localhost ip6-loopback
> fe00::0 ip6-localnet
> ff00::0 ip6-mcastprefix
> ff02::1 ip6-allnodes
> ff02::2 ip6-allrouters
> ff02::3 ip6-allhosts
Mir ist auch rätselhaft weswegen da 127.0.1.1 drin steht. Ich hab nicht dran rumgefummelt, das scheint in Ubuntu 7.04 standardmäßig so zu laufen. Ich hab gerade auf dem anderen Rechner (deskpro, 192.168.2.17) nachgeschaut, dort ist der Name auch auf 127.0.1.1 gemapped.

Habe jetzt auch mit PHP und C mal Versuche gemacht, selbes Resultat.

Wie dem auch sei, mit dem 127.0.1.1 verstehe ich warum unter root der Port 86 in obigem Beispiel nicht nach außen hin geöffnet wurde.

Der Client-Rechner den ich verwende ist bestimmt richtig konfiguriert. Jeder andere Dienst funktioniert ja auch (/home via NFS mounten, Benutzerauthentifizierung via LDAP, HTTP, FTP, BattleNet-Gaming-Server).
Wenn ich's nicht als root ausführe werden die Ports 8117 bis 8122 gebunden, wie zuvor ist allerdings keiner sichtbar für die Außenwelt.
Die Ausgabe von netstat sagt was anderes. Hast du deinen "Server" mal gescannt? Mindestens 192.168.2.23:8117 muss öffentlich verfügbar sein.
Diese Ausgabe habe ich aufgerufen als das Script als root lief. Als normaler User sieht's aber genauso aus, nur dass eben 81 bis 86 nicht auftauchen.

192.168.2.23:8117 ist definitiv nicht öffentlich verfügbar, ich prüfe das auch mit 'nem Portscan (daher weiß ich auch dass 82 bis 85 sichtbar waren, der Rest eben nicht).

Ich bin mit meinem latein total am Ende.

Das hier ist übrigens das Server-Script welches ich geschrieben habe:

Code: Alles auswählen

from threading import *
from SimpleXMLRPCServer import SimpleXMLRPCServer
import hashlib
import random
import xmlrpclib
import sys
import time
import socket
from Queue import *

# Helping classes

class Config:
	def __init__(self, **dict):
		for i in dict:
			self.__dict__[i] = dict[i]

users = {"warhog": "qwe123", "martin": "asd456"}

cfg = Config(port=8117, host="0.0.0.0")

sessions = {}
class Session:
	def __init__(self, server):
		self.id = hashlib.md5(str(random.randint(0, 16777216))).hexdigest()
		self.server = server
		sessions[self.id] = self
		self.auth = False
		self.update()

	def check(self):
		if self.time + 300 > time.time():
			self.update()
			return True
		else:
			self.delete()
			return False

	def update(self):
		self.time = time.time()
	
	def delete(self):
		del sessions[self.id]

	def authed(self):
		return self.auth

	def auth_ok(self, ok):
		self.auth = not not ok


tickets = {}
class Ticket:
	def __init__(self, sender):
		self.id = hashlib.md5(str(random.randint(0, 16777216))).hexdigest()
		self.sender = sender
		tickets[self.id] = self

	def punch(self):
		del tickets[self.id]


# These are RPC-functions

def request(sessionID, action, argument):
	if (ok(sessionID)):
		ticket = Ticket(sessionID)
		q.put((ticket.id, action, argument))
		return ticket.id
	else:
		return False

def hai(server, port=8119):
	session = Session((server, port))
	return session.id

def bye(sessionID):
	"logout"
	if ok(sessionID):
		sessions[sessionID].delete()
		return True
	else:
		return False

def auth(sessionID, user, password=""):
	"authentication of a user"
	if sessionID not in sessions:
		return False
	if user in users:
		if users[user] == password:
			sessions[sessionID].auth_ok(True)
			return True
	return False

def login(user, password=None, server="localhost", port=8119):
	"does 'hai' and 'auth' at once"
	sid = hai(server, port)
	if auth(sid, user, password):
		return sid
	else:
		return False

def status(sessionID):
	return {"sessions": sessions, "tickets": tickets}

# These are helping functions

def ok(sessionID):
	if sessionID in sessions:
		if sessions[sessionID].check():
			return sessions[sessionID].authed()
		else:
			return False
	else:
		return False


# The thing to do

q = Queue()
l1 = Lock() # output lock
l2 = Lock() # access to sessions/tickets lock

def log(message):
	print message

def zensor():
	w = q.get()
	l1.acquire()
	log("S "+w[0]+"("+w[1]+", "+w[2]+")")
	l1.release()
	time.sleep(20)
	l1.acquire()
	log("F "+w[0])
	l1.release()
	q.task_done()

for i in range(5):
	t = Thread(target=zensor)
	t.setDaemon(True)
	t.start()

server = SimpleXMLRPCServer((cfg.host, cfg.port))
server.register_introspection_functions()
server.register_function(hai)
server.register_function(auth)
server.register_function(request)
server.register_function(bye)
server.register_function(status)
server.register_function(login)
server.serve_forever()
'nen Client-Script gibt's noch nicht, ich teste immer alles in der interaktiven Shell, ungefähr so

Code: Alles auswählen

import xmlrpclib
s = xmlrpclib.Server("http://localhost:8117");
sid = s.hai("localhost");
s.auth(sid, "warhog", "qwe123");
s.status(sid);
lokal funktioniert das, von nem anderen rechner aus nicht. Ich hab das mittlerweile auch schon in nem interaktiven Python-Shell auf nem Windows-XP versucht. Der Port ist eben definitv nur lokal sichtbar!

gruß, Warhog
BlackJack

Das mit der IP in `/etc/hosts` kann ich bestätigen:

Code: Alles auswählen

bj@s8n:~$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       s8n
...
Anmerkungen zum Quelltext:

Die `Config` kannst Du einfacher machen. Ein ``self.__dict__.update(dict)`` reicht statt der Schleife. Wobei `dict` ein ungünstiger Name ist, weil man damit den Namen für den `dict`-Typ verdeckt.

Die `sessions` und `tickets` sollte man aus der Modul-Ebene entfernen und Klassenvariablen daraus machen.

Bei der ID ist das MD5 völlig überflüssig, es würde eine Zufallszahl reichen. Wenn es unter den 16777216 Möglichkeiten Kollisionen gäbe, würde MD5 sogar nicht nur überflüssig, sondern auch noch ungünstiger sein. Hier wäre ein ``'%s|%s' % (id(self), time.time())`` geeigneter und sicherer als eine Zufallszahl. Wenn Zufall, dann am besten UUIDs aus dem `uuid`-Modul.

`authed()` und `auth_ok()` sind überflüssige Methoden. Da kann man auch direkt auf das `auth`-Attribut zugreifen. Wobei ``self.auth = not not ok`` ja wohl ein "WTF?" ist. :-)

Die Verständlickeit `zensor()` wäre grösser wenn man den Objekten aus der Queue einzelne Namen gibt, statt über Indizes darauf zuzugreifen.
Warhog
User
Beiträge: 11
Registriert: Samstag 26. Januar 2008, 21:53
Kontaktdaten:

Hi,

danke BlackJack für die Verbesserungsvorschläge. Ist mein erstes Python-Script, hab mich letzte Woche dafür entschieden ein wenig damit rumzuprobieren - bin bisher mehr als nur positiv überrascht :)

Die Config-Klasse ist erstmal nur Platzhalter, die soll später die Konfiguration aus einer Datei lesen - ebenso wie die users-Variable.

Die md5-Strings find ich um ehrlich zu sein einfach nur so schön cryptisch *g* allerdings werd' ich wohl wegen evtl. Kollisionsgefahr das ganze wirklich umauen..

ähm ja *hüstel* not not .. was hab ich da gemacht ;D ursprünglich hätte inder Variablen ok entweder False gestanden oder eine Session-ID (hat sich dann im Zuge des schreibens geändert), da ich wie gesagt neu bin und keinen Plan von der Typisierung in Python hab, hab ich einfach auf diesen "Trick" zurückgegriffen (so mach ichs meistens in JavaScript) - einfach ok zuweisen läuft im Endeffekt auf ne Kopie der Session-ID zurück (in der eigentlichen Variante), so ist's auf jeden Fall boolean. Habs jetzt aber herausgenommen und auch nur die auth-Variable verwendet.

Stichwort Klassenvariablen: Die Objektorientierung in Python leuchtet mir noch nicht komplett ein. Wenn ich

Code: Alles auswählen

class x():
  var = 10
deklariere, ist dann "var" eine statische Klassenvariable?

Ich hab jetzt nochmal in C eine listening TCP-Socket aufgesetzt und auf meinen beiden Ubuntu 7.04 Systemen hier getestet. Das eine ist quasi Installationsfrisch, das andere das welches ich hier verwende. In beiden offenbart sich das unerwartete Verhalten in allen Programmiersprachen die ich jetzt ausgetestet habe gleich - Perl, C, PHP, selben Symptome. Ich hab also nochmal herumgetestet und um den sensiblen Port 1024 herumgetestet. Unterhalb 1024 benötige ich wie bekannt root-Rechte, aber der Port ist sichtbar. Ich habe nach obenhin weiter getestet und folgende interessante Feststellung gemacht: Bis inklusive Port-Nummer 7000 kann ich an Ports binden die dann auch offen sind, darüber geht nichts mehr. Mir ist absolut schleierhaft wieso das so ist, es muss aber an der von-Haus-aus-Konfiguration von Ubuntu 7.04 liegen - denn es ist auf beiden Systemen haargenau dasselbe.

Somit ist das Problem für mich prinzipiell gegessen, ich benutze jetzt Port 6766 (laut IANA unassigned ;D) und fertig ist die Geschichte. Trotzdem seltsam... keinen Plan was da los ist. Vor allen Dingen weil 7000 aus rechner-sicht ja nun absolut keine besondere Zahl ist.

Das Script sieht jetzt übrigens so aus:

Code: Alles auswählen

from threading import *
from SimpleXMLRPCServer import SimpleXMLRPCServer
import hashlib
import random
import xmlrpclib
import sys
import time
import socket
from Queue import *

# Helping classes

# to be replaced by reading a config-file
class Config:
	def __init__(self, **dic):
			self.__dict__.update(dic)

# to be replaced by LDAP-Queries
users = {"warhog": "qwe123", "martin": "asd456"}

cfg = Config(port=6766, host="0.0.0.0", clientPort=6717, maxQueue=3)

def makeID():
	return "%X%X" % (random.randint(0, 16777216), time.time())

sessions = {}
class Session:
	def __init__(self, server):
		self.id = makeID()
		self.server = server
		sessions[self.id] = self
		self.auth = False
		self.update()

	def check(self):
		if self.time + 300 > time.time():
			self.update()
			return True
		else:
			self.delete()
			return False

	def update(self):
		self.time = time.time()
	
	def delete(self):
		del sessions[self.id]


tickets = {}
class Ticket:
	def __init__(self, sender):
		self.id = makeID()
		self.sender = sender
		tickets[self.id] = self

	def punch(self):
		del tickets[self.id]


# These are RPC-functions

def request(sessionID, action, argument):
	"request something"
	if (ok(sessionID)):
		ticket = Ticket(sessionID)
		q.put({"ticket": ticket.id, "action": action, "argument": argument})
		return ticket.id
	else:
		return False

def hai(server, port=cfg.clientPort):
	"ping pong ;)"
	session = Session((server, port))
	return session.id

def bye(sessionID):
	"logout"
	if ok(sessionID):
		sessions[sessionID].delete()
		return True
	else:
		return False

def auth(sessionID, user, password=""):
	"authentication of a user"
	if sessionID not in sessions:
		return False
	if user in users:
		if users[user] == password:
			sessions[sessionID].auth = True
			return True
	return False

def login(user, password=None, server="localhost", port=cfg.clientPort):
	"does 'hai' and 'auth' at once"
	sid = hai(server, port)
	if auth(sid, user, password):
		return sid
	else:
		return False

def status(sessionID):
	return {"sessions": sessions, "tickets": tickets}

# These are helping functions

def ok(sessionID):
	if sessionID in sessions:
		if sessions[sessionID].check():
			return sessions[sessionID].auth
		else:
			return False
	else:
		return False


# The thing to do

q = Queue()
l1 = Lock() # output lock
l2 = Lock() # access to sessions/tickets lock

def log(message):
	print message

def zensor():
	w = q.get()
	l1.acquire()
	log("S "+w['ticket']+"("+w['action']+", "+w['argument']+")")
	l1.release()
	time.sleep(20)
	l1.acquire()
	log("F "+w[0])
	l1.release()
	q.task_done()

for i in range(cfg.maxQueue):
	t = Thread(target=zensor)
	t.setDaemon(True)
	t.start()

server = SimpleXMLRPCServer((cfg.host, cfg.port))
server.register_introspection_functions()
server.register_function(hai)
server.register_function(auth)
server.register_function(request)
server.register_function(bye)
server.register_function(status)
server.register_function(login)
server.serve_forever()
gruß und schönen Sonntag noch, Warhog
BlackJack

Wenn Du von einem Objekt den Wahrheitswert haben willst: ``bool(obj)``. Das ist etwas direkter als doppelte Negierung. :-)

Klassenvariable: Jup, das ist dann ein Attribut der Klasse über dass Du mit ``x.var`` zugreifen kannst. Wenn es nur lesender Zugriff ist, und kein neu binden des Attributs, geht es von innerhalb der Methoden auch mit ``self.var``. Wenn ein Attribut nicht im Objekt gefunden wird, wird als nächstes in der Klasse nachgeschaut.
Antworten