Vortrag zur Socket-Programmierung von Dual-Stack-Anwendungen in Python von der PyCon DE 2011 Leipzig.
http://de.pycon.org/2011/schedule/sessions/42/
http://blip.tv/episode/5632211
1. Socket-Programmierung mit IPv6
Christian Kauhaus
gocept gmbh & co. kg
PyCon DE Leipzig, 7. Oktober 2011
Socket-Programmierung mit IPv6 · Christian Kauhaus · 1
3. Einleitung
Anti-Motivation
Brauche ich IPv6-Socketprogrammierung?
In der Regel nicht.
IPv6-Support umsonst durch:
• Bibliotheken (urllib etc.)
• Frameworks (Twisted etc.)
• vorgelagerter Server (nginx etc.)
Aber wenn ich es doch mal brauche..?
Socket-Programmierung mit IPv6 · Christian Kauhaus · 3
4. Durchgehendes Beispiel
Christians DATE-Protokoll
Simples Protokoll, um das aktuelle Datum abzufragen
• Client sagt: DATE
• Server sagt: DATE YYYY-MM-DD
class DateHandler(socketserver.BaseRequestHandler):
def handle(self):
if self.request.recv(1024).strip() == 'DATE':
self.request.send('DATE %srn' %
datetime.date.today())
else:
self.request.send('ERRORrn')
class DateSocketServer(socketserver.ThreadingMixIn,
socketserver.TCPServer):
allow_reuse_address = True
Socket-Programmierung mit IPv6 · Christian Kauhaus · 4
5. Aktive Socketverbindungen
Date-Client nur mit IPv4
»Old school« Implementierung
class DateClient(object):
def __init__(self, hostname, port=9000, timeout=15):
address = socket.gethostbyname(hostname)
self.socket = socket.socket(socket.AF_INET,
socket.SOCK_STREAM, 0)
self.socket.settimeout(timeout)
self.socket.connect((address, port))
def query(self):
self.socket.send('DATErn')
response = self.socket.recv(1024)
print 'C: response is %s' % response.strip()
Socket-Programmierung mit IPv6 · Christian Kauhaus · 5
6. Verhalten des Single-Stack-Clients
Ignoriert IPv6-Adressen komplett
Shell-Demo:
• test.local löst zu IPv4- und IPv6-Adressen auf
• server6 test.local lauscht auf allen Adressen
• client4 test.local verbindet sich über IPv4
Socket-Programmierung mit IPv6 · Christian Kauhaus · 6
7. Programmiertip 1: getaddrinfo()
Schnittstelle zwischen Sockets und Außenwelt
getaddrinfo() nutzen. Immer!
.
GAI
• Namensauflösung
• Validierung der Eingabe
• Filtern nach Socket-Typen
• address family, socket type, IP protocol
Anti-Pattern
address = socket.gethostbyname(host)
.
Socket-Programmierung mit IPv6 · Christian Kauhaus · 7
8. Programmiertip 1: getaddrinfo()
Schnittstelle zwischen Sockets und Außenwelt
Beispiel:
>>> socket.getaddrinfo('www.python.org', 80, socket.AF_UNSPEC)
[(10, 1, 6, '', ('2001:888:2000:d::a2', 80, 0, 0)),
(10, 2, 17, '', ('2001:888:2000:d::a2', 80, 0, 0)),
(10, 3, 0, '', ('2001:888:2000:d::a2', 80, 0, 0)),
(2, 1, 6, '', ('82.94.164.162', 80)),
(2, 2, 17, '', ('82.94.164.162', 80)),
(2, 3, 0, '', ('82.94.164.162', 80))]
• address family, z. B. 10 = AF_INET6
• socket type, z. B. 1 = SOCK_STREAM
• IP protocol, z. B. 6 = SOL_TCP
• socket address: host, port, flow id, scope
Socket-Programmierung mit IPv6 · Christian Kauhaus · 8
9. Programmiertip 2: 1:n-Auflösung
Ein Host, mehrere Adressen
Einem Hostnamen sind mehrere Adressen zugeordnet.
.
• IPv4- und IPv6-Adresse 1:n
• mehrere IPv6-Adressen für Multi-Homed Hosts
• getaddrinfo() gibt eine Liste zurück
• Reihenfolge nach absteigender Priorität
Anti-Pattern
res = socket.getaddrinfo(...)
sock.connect(res[0][4])
.
Socket-Programmierung mit IPv6 · Christian Kauhaus · 9
10. Programmiertip 3: Connect-Loop
Alle Adressen durchprobieren
Abbruch nur, wenn keine Adresse erreichbar ist.
.
Loop
• Verbindung kommt zustande: fertig
• Fehler, aber noch Adressen übrig: weitermachen
• Fehler bei letzter Adresse: letzten Fehler eskalieren
Anti-Pattern
try:
sock.connect(sockaddr)
except socket.error as e:
sys.exit(1)
.
Socket-Programmierung mit IPv6 · Christian Kauhaus · 10
11. Dual-Stack-Code
Date-Client mit IPv4 und IPv6
class DateClient(object):
def __init__(self, hostname, port=9000, timeout=15):
self.socket = None
self.connect(hostname, port, timeout)
def query(self):
self.socket.send(b'DATErn')
response = self.socket.recv(1024).decode()
print('C: response is {}'.format(response.strip()))
Socket-Programmierung mit IPv6 · Christian Kauhaus · 11
12. Dual-Stack-Code
Date-Client mit IPv4 und IPv6 (Fortsetzung)
def connect(self, host, port, timeout): GAI
.
exception = None
for (af, socktype, proto, cname, sockaddr
1:n . ) in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM, 0):
try:
self.socket = socket.socket(af, socktype, proto)
self.socket.settimeout(timeout)
self.socket.connect(sockaddr)
return
except socket.error as e:
exception = e
raise exception .Loop
.
Socket-Programmierung mit IPv6 · Christian Kauhaus · 12
13. Verhalten des Dual-Stack-Clients
Verbindung mit IPv6
Shell-Demo:
• test.local löst zu IPv4- und IPv6-Adressen auf
• server6 test.local lauscht auf allen Adressen
• client6 test.local verbindet sich über IPv6
• server4 test.local lauscht nur auf IPv4-Adresse
• client6 test.local probiert IPv6 und verbindet sich dann
über IPv4
Socket-Programmierung mit IPv6 · Christian Kauhaus · 13
14. Passive Socketverbindungen
Date-Server mit IPv4
class DateServer(object):
def __init__(self, listen_addresses, port):
self.listen_addresses = listen_addresses
self.port = port
self.threads = list(self.create_threads())
def create_threads(self):
for host_or_address in self.listen_addresses:
address = socket.gethostbyname(host_or_address)
server = DateSocketServer((address, self.port),
DateHandler)
yield threading.Thread(target=server.serve_forever)
def run(self):
for thread in self.threads:
thread.start()
for thread in self.threads:
thread.join()
Socket-Programmierung mit IPv6 · Christian Kauhaus · 14
15. Verhalten des Single-Stack-Servers
Verbindung mit IPv4
Shell-Demo:
• test.local löst zu IPv4- und IPv6-Adressen auf
• server4 test.local lauscht nur auf IPv4-Adresse
• client6 test.local verbindet sich über IPv4
Socket-Programmierung mit IPv6 · Christian Kauhaus · 15
16. Dual-Stack-Code
Date-Server mit IPv4 und IPv6
create_threads() muss mehrere Server pro Listen-Item
erzeugen:
class DateServer(object):
def __init__(self, listen_addresses, port):
self.listen_addresses = listen_addresses
self.port = port
self.threads = list(self.create_threads())
def create_threads(self):
servers = []
for host_or_address in self.listen_addresses:
servers += self.connect_servers(host_or_address)
for server in servers:
yield threading.Thread(target=server.serve_forever)
Socket-Programmierung mit IPv6 · Christian Kauhaus · 16
17. Dual-Stack-Code
Date-Server mit IPv4 und IPv6 (Fortsetzung)
def connect_servers(self, host_or_address):
servers = []
exception = None
.GAI
for (af, socktype, proto, cname, sockaddr
) in socket.getaddrinfo(
. host_or_address, self.port, socket.AF_UNSPEC,
1:n socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
try:
servers.append(DateSocketServer(
sockaddr[0:2], DateHandler, True, af))
except socket.error as e:
exception = e
continue
.Loop
if not servers:
raise exception
return servers
.
Socket-Programmierung mit IPv6 · Christian Kauhaus · 17
18. Dual-Stack-Code
Date-Server mit IPv4 und IPv6 (Fortsetzung)
TCPServer erweitern, um address_family im Konstruktor
fallweise zu setzen:
class DateSocketServer(socketserver.ThreadingMixIn,
socketserver.TCPServer):
allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass,
bind_and_activate=True,
address_family=socket.AF_INET):
self.address_family = address_family
super().__init__(server_address, RequestHandlerClass,
bind_and_activate)
Socket-Programmierung mit IPv6 · Christian Kauhaus · 18
19. Verhalten des Dual-Stack-Servers
Verbindung mit IPv6 und IPv4
Shell-Demo:
• test.local löst zu IPv4- und IPv6-Adressen auf
• server6 test.local lauscht auf allen Adressen
• client6 test.local verbindet sich über IPv6
• client4 test.local verbindet sich über IPv4
Socket-Programmierung mit IPv6 · Christian Kauhaus · 19
20. IP-Adressen
Gar nicht so simpel
IP-Adressen als Strings
entweder:
• unbehandelt durchreichen
(z. B. getaddrinfo(), Datenbanken, Logging)
oder:
• »richtig« parsen und manipulieren (z. B. netaddr, IPy)
Anti-Pattern
re.match(r'[0-9]+.[0-9]+.[0-9]+.[0-9]+', ipaddress)
Socket-Programmierung mit IPv6 · Christian Kauhaus · 20
21. Zusammenfassung
Socket-Programmierung mit IPv6
• Sockets richtig erzeugen
• getaddrinfo()
• 1:n Adressauflösung
• Connect-Loop
• Externe Repräsentation von IP-Adressen mit Respekt
behandeln
• Dual-Stack Code schreiben! :-)
Socket-Programmierung mit IPv6 · Christian Kauhaus · 21
22. Vielen Dank!
Fragen?
E-Mail kc@gocept.com
Jabber kc@gocept.com
Hosting http://gocept.net/
auf Jobsuche? http://gocept.com/das-unternehmen/karriere
API definition RFC 3493
IPv6 porting http://owend.corp.he.net/ipv6/
Socket-Programmierung mit IPv6 · Christian Kauhaus · 22