Ich betreibe nun schon seit…. Oha…. Seit langem 🙂 einen eigenen Jabber-Server.
Jetzt ist das Teil alles andere als ein großes System mit mehreren tausend Usern. Er wird genutzt von der Familie, ein paar Freunden und Bekannten. Mehr sollte und soll es auch nie werden. Vor knapp zwei Jahren habe ich auf Openfire gesetzt. Openfire bietet eine recht ausgereifte klickibunti Oberfläche. Leider kamen in der letzten Zeit immer mal wieder nervige Bugs hinzu.
Zum einen ist kein Paket für meine Umgebung dabei. Also muss ich das Ding immer von Hand reinwursten. Dann hat es mich einiges an Überzeugungsarbeit gekostet das Openfire nicht als root laufen will, wer will schon Software als root ausführen? Dann waren da noch ein paar Bugs bezüglich SSL in der Server zu Server Kommunikation und diesen nervigen Dialback errors….. Nun scheint die aktuelle Version: 3.7.1 sowie auch das nightly build 2011-12-21 ein Problem mit IPv6 und DNS zu haben. So genau bin ich auch jetzt nicht dahinter gekommen.
Wie auch immer! Das Kraken – IM Gateway scheint leicht eingeschlafen zu sein, Openfire selbst ist in seiner Weiterentwicklung auch etwas seltsam. Vielleicht hat das Projekt geforkt und ich habe es verpasst? Ist ja auch schon vorgekommen, zuletzt verpasste ich StarOffice zu OpenOffice (peinlich peinlich). Da mir nun drei mal ein Bug bei Openfire so vor das Scheinbein getreten hat, dass keine nutzbare Funktion von Jabber übergeblieben ist (zumindest so wie ich sie mir vorstellen, also mit IPv6 und Verschlüsselung. Habe ich mich dazu entschieden zu Ejabberd zu wechseln.
Ejabberd hat fertige Pakete für meine Umgebung im Repository. Somit kann ich auch auf einfache Sicherheitsupdates hoffen. Transporter für ICQ / MSN… sind selbstverständlich überhaupt kein Problem und da Facebook (Familie ihr wisst schon….) direkt per Jabber erreichbar ist, ist alles gut 🙂
Meine paar Konten habe ich recht schnell aus Openfire und in Ejabberd bekommen. Nun läuft mein Messanger also über Ejabberd.
2012.05.03 13:40:26 org.jivesoftware.openfire.session.LocalOutgoingServerSession - Error authenticating domain with remote server: domain.org
java.lang.NumberFormatException: For input string: "4860:4860::8888"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Integer.parseInt(Integer.java:458)
at java.lang.Integer.parseInt(Integer.java:499)
at com.sun.jndi.dns.DnsClient.<init>(DnsClient.java:105)
at com.sun.jndi.dns.Resolver.<init>(Resolver.java:44)
at com.sun.jndi.dns.DnsContext.getResolver(DnsContext.java:553)
at com.sun.jndi.dns.DnsContext.c_getAttributes(DnsContext.java:413)
at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_getAttributes(ComponentDirContext.java:213)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:121)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:109)
at javax.naming.directory.InitialDirContext.getAttributes(InitialDirContext.java:123)
at org.jivesoftware.openfire.net.DNSUtil.srvLookup(DNSUtil.java:199)
at org.jivesoftware.openfire.net.DNSUtil.resolveXMPPDomain(DNSUtil.java:131)
at org.jivesoftware.openfire.session.LocalOutgoingServerSession.createOutgoingSession(LocalOutgoingServerSession.java:269)
at org.jivesoftware.openfire.session.LocalOutgoingServerSession.authenticateDomain(LocalOutgoingServerSession.java:167)
at org.jivesoftware.openfire.server.OutgoingSessionPromise$PacketsProcessor.sendPacket(OutgoingSessionPromise.java:261)
at org.jivesoftware.openfire.server.OutgoingSessionPromise$PacketsProcessor.run(OutgoingSessionPromise.java:238)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Ich bin nur sehr selten an einem Ort, an welchem ich keine Internetverbindung nutzen kann. Noch seltener würde ich genau dann eine Internetverbindung benötigen. Wenn es dann aber so ist, dann benötige ich sie wirklich!
Nun komme ich in der letzten Zeit immer mal wieder an diese Stelle und ärgere mich. Oft ist zwar ein Kollege oder Bekannter in der Nähe, mit so einem feinen Android Mobiltelefon, nur hilft mir dieses beim Arbeiten auf einer SSH-Shell weniger. Ja, es geht aber wirkliches Arbeiten geht nicht… Zudem bin ich ein Mensch der seinen Windowmanager benutzt, sprich viele offene Fenster. Auf so einem kleinen Mobilding ist mir mehr als eine kurze E-Mail oder etwas Google klicker klacker einfach zu aufwändig.
Das Handy also als Modem mit dem Rechner verbinden? So selten wie ich es im Moment benötige, mich direkt 1 Jahr an einen 20€/Monat Tarif meines Anbieters zu binden? Ne, so geht das nicht….
Vor kurzem war ich nun im Blödmarkt unterwegs. Da lagen in der Grabbelkiste so 15 Euronen O2 Prepaid USB-Sticks.
Dem etwas überforderten Fachberater für die Dinger konnte ich mit etwas Mühe die Information entlocken, dass ich über dieses Angebot „echtes“ Internet erhalte. Damit ist gemeint, dass ich SSH-Sessions auf beliebigen Ports öffnen kann und auch mein IPv6 Tunnelbroker funktionieren sollte. ….Nebenbei, habt ihr im Blödmarkt mal gefragt ob ihr über was auch immer eine Verbindung zu einen IPv6 Tunnelbroker aufbauen könnt? Macht mal, ist lustig.
HTTP / SMTP / IMAP mit und ohne SSL/TLS alles kein Problem!
Mit der 5 Tage x 3,50€ = 17,50€ Sollte einem Test nichts im Wege stehen. Keine Grundgebühr oder sonstige laufenden Kosten… Ich brauche es nicht, ich zahle es nicht. Wenn ich also feststelle das ich es doch oft und gut nutze, so dass sich einer der Knebelverträge des Anbieters meines Vertrauens lohnen würde, dann kann ich das Teil wegwerfen und mich bewusst knebeln lassen. Anderweitig habe ich eine tolle Lösung für den Notfall!
Beim Kauf habe ich jetzt nicht darauf geachtet ob das Teil nun unter bzw. mit meinem Linux (Gentoo) zusammenarbeitet. Den Fachberater im Blödmarkt wollte ich es nun nicht noch fragen, er schien jetzt schon von mir genervt zu sein!
Hey, gut wie ich bin, bekomme ich das Teil schon zum rennen (Boar ist diese Selbstüberschätzung nicht wiederlich?)!
Da meine Frau etwas von: „Rausgeworfenes Geld…. Überflüssiges Spielzeug… und du sitzt eh viel zu viel vor dem Computer!“ murmelte…. _MUSS_ das Teil einfach Laufen.
Tut es nur so ~out of the box~ nicht! Dass hat man nun also davon, man kauft im Blödmarkt halt nichts. Vor allem nicht ohne Verstand, oder gerade deswegen? Wie auch immer, so haben ich es ans Laufen bekommen!
Ich habe hier also den O2 Surfstick MF190 von der Firma ZTE.
Stecke ich diesen einfach in mein System ein und schaue mir an was der Kernel dazu sagt, sehe ich folgendes:
$ dmesg
usb 2-1: new high speed USB device using ehci_hcd and address 3
usb 2-1: New USB device found, idVendor=19d2, idProduct=0083
usb 2-1: New USB device strings: Mfr=3, Product=2, SerialNumber=4
usb 2-1: Product: ZTE WCDMA Technologies MSM
usb 2-1: Manufacturer: ZTE,Incorporated
usb 2-1: SerialNumber: P671A2TMED010000
scsi7 : usb-storage 2-1:1.0
scsi 7:0:0:0: CD-ROM ZTE USB SCSI CD-ROM 2.31 PQ: 0 ANSI: 2
sr1: scsi-1 drive
sr 7:0:0:0: Attached scsi CD-ROM sr1
sr 7:0:0:0: Attached scsi generic sg2 type 5
Der Stick wird also als USB-CDROM Laufwerk (hier liegt die Software für die Windows User) und USB-Festplatte (sofern eine MicroSD-Karte eingelegt ist, wäre es diese) erkannt.
Mal schauen was ein lsusb sagt:
$ lsusb
Bus 002 Device 004: ID 19d2:0083 ONDA Communication S.p.A
19d2 steht für den Hersteller und 0083 für das Gerät selbst. Google sagt 0083 ist der Stick aber im Modus (ich nenne es mal) Datenlaufwerk. Ich will aber Modem 🙂 Hierzu sagt Google man muss ein paar bestimmte Kommandos schicken und schon wechselt der USB-Stick seinen Modus. Findige Leute haben sich da schon einen Kopf zu gemacht und das Programm: usb_modeswitch geschrieben. Also soll emerge mal loslegen:
$ emerge usb_modeswitch
Solange er rechnet könnte ich meinen Kernel ja schon mal davon überzeugen das Gerät zu „ignorieren“. Dazu füge ich in die Datei: /usr/src/linux/drivers/usb/storage/unusual_devs.h folgende Zeilen ein:
Damit Geräte dieser Art überhaupt funktionieren können (und ich nach der Änderung in unusual_devs.h ja eh neu kompilieren muss) sollte die Kernelkonfiguration wie folgt angepasst werden:
Device Drivers ->
USB support --->
<M> OHCI HCD support (If not use Intel or VIA chipset)
<M> UHCI HCD (most Intel and VIA) support (If use Intel or VIA chipset)
<M> USB Serial Converter support --->
[*] USB Generic Serial Driver
<M> USB driver for GSM and CDMA modems
Network device support --->
<*> PPP (point-to-point protocol) support
<*> PPP support for async serial ports
Inzwischen ist usb_modeswitch fertig. Für meinen Stick muss ich leider noch etwas Handarbeit leisten. Denn in der Datei /lib/udev/rules.d/40-usb_modeswitch.rules müssen noch folgende Zeilen hinzugefügt werden:
Damit die neue Regel zur Anwendung kommen noch schnell ein:
$ udevadm control --reload-rules
Jetzt sollte es beim Einstecken vom Stick schon anders aussehen….
$ dmesg
usb 2-1: new high speed USB device using ehci_hcd and address 4
usb 2-1: New USB device found, idVendor=19d2, idProduct=0117
usb 2-1: New USB device strings: Mfr=3, Product=2, SerialNumber=4
usb 2-1: Product: ZTE WCDMA Technologies MSM
usb 2-1: Manufacturer: ZTE,Incorporated
usb 2-1: SerialNumber: P671A2TMED010000
usb-storage 2-1:1.0: device ignored
usb-storage 2-1:1.1: device ignored
usb-storage 2-1:1.2: device ignored
usb-storage 2-1:1.3: device ignored
usbcore: registered new interface driver usbserial
USB Serial support registered for generic
usbcore: registered new interface driver usbserial_generic
usbserial: USB Serial Driver core
USB Serial support registered for GSM modem (1-port)
option 2-1:1.0: GSM modem (1-port) converter detected
usb 2-1: GSM modem (1-port) converter now attached to ttyUSB0
option 2-1:1.1: GSM modem (1-port) converter detected
usb 2-1: GSM modem (1-port) converter now attached to ttyUSB1
option 2-1:1.2: GSM modem (1-port) converter detected
usb 2-1: GSM modem (1-port) converter now attached to ttyUSB2
usbcore: registered new interface driver option
option: v0.7.2:USB Driver for GSM modems
Wohooo ein GSM Modem. Was sagt lsusb?
$ lsusb
Bus 002 Device 004: ID 19d2:0117 ONDA Communication S.p.A.
OK, der Stick wird nun also als Modem erkannt, die Datenlaufwerke werden ignoriert und ich könnte mich ja mal um eine Interneteinwahl kümmern, oder? Nötig ist dafür ppp und (weil es so schön einfach ist) wvdial. Emerge muss das wieder für mich erledigen:
$ emerge ppp wvdial
Nach kurzer Zeit ist er fertig. Nun lege ich mal das ppp Device an:
$ mknod /dev/ppp c 108 0
*umschau* ich habe ja so ein paar Tests hinter mir und Scripte können da knallhart sein. Wenn man denen sagt: „Probiere mal bis geht…“ Dann tun die das auch wenn sie 100 mal die falsche PIN eingeben. Man kann lange suchen bis man darauf kommt die PUK einzugeben. SEHR lange. Ich habe also die PIN-Eingabe abgeschaltet!
wvdial ist schnell konfiguriert. Meine Konfiguration für O2 schaut so aus:
Stecke ich nun meinen (mit abgeschalteter PIN-Eingabe) Surfstick ins Notebook beginnt er rot zu leuchten. Ist der Stick betriebsbereit und hat Netz beginnt er grün zu leuchten 🙂 Einfach, oder?
Die Einwahl funktioniert nun recht einfach mit wvdial:
$ wvdial o2
--> WvDial: Internet dialer version 1.61
--> Cannot get information for serial port.
--> Initializing modem.
--> Sending: ATZ
ATZ
OK
--> Sending: ATQ0 V1 E1 S0=0 &C1 +FCLASS=0
ATQ0 V1 E1 S0=0 &C1 +FCLASS=0
OK
--> Sending: AT+ZOPRT=5
AT+ZOPRT=5
OK
--> Sending: AT+CGDCONT=1,"IP","pinternet.interkom.de"
AT+CGDCONT=1,"IP","pinternet.interkom.de"
OK
--> Modem initialized.
--> Sending: ATD*99#
--> Waiting for carrier.
ATD*99#
CONNECT 7200000
--> Carrier detected. Starting PPP immediately.
--> Starting pppd at Tue Mar 8 20:35:06 2011
--> Pid of pppd: 22068
--> Using interface ppp0
--> local IP address 10.151.95.132
--> remote IP address 10.64.64.64
--> primary DNS address 193.189.244.225
--> secondary DNS address 193.189.244.206
Nun sollte man auch schon online sein. Ein kurzer Blick auf die Interfacekonfiguration zeigt:
Jabber, offiziell XMPP, ist ein offenes Messaging-Protokoll. Kein zentraler Betreiber, kein Vendor Lock-in, kein Unternehmen das die Nutzungsbedingungen diktiert. Jeder kann einen eigenen Server betreiben, und die Server sprechen untereinander. Wie E-Mail, nur für Messaging.
Warum ein eigener Server
Bei kommerziellen Messengern gibt man mit der Nutzung Rechte an seinen Inhalten ab. Die AGBs von AIM, ICQ, MSN und Co. erlaubten dem Betreiber die Verwertung aller Inhalte die über den Dienst liefen. Die Dienste gibt es größtenteils nicht mehr, aber das Muster ist geblieben. Ein eigener Server bedeutet: Eigene Regeln, eigene Daten, eigene Entscheidung welche Module aktiv sind.
Die Vorteile von XMPP: Open Source, Verschlüsselung per TLS, kein Single Point of Failure, und eine riesige Auswahl an Clients für jede Plattform.
Openfire
Nach Tests mit jabberd, ejabberd und Openfire bin ich bei Openfire hängengeblieben. Für einen kleinen Server mit Familie und Freunden bringt Openfire alles mit: Weboberfläche zur Administration, Plugin-System, IPv6-Support und einfache Installation unter Debian. Für einen großen öffentlichen Server würde ich anders entscheiden, aber für meinen Zweck passt es.
Um zu zeigen wie flexibel XMPP ist: Mein Siemens Gigaset C470IP hat ein Mobilteil C47H mit eingebautem Messenger. Das Telefon verbindet sich mit dem Jabber-Server und kann Nachrichten empfangen und verschicken. Ohne App, ohne Smartphone, direkt auf dem DECT-Telefon.
Der Jabber Messenger des Gigaset C47H ist online und mit dem Server verbunden.
Am Gigaset C47H ist eine neue Jabber-Nachricht angekommen.
Die Nachricht wird am Mobilteil gelesen. Antworten funktioniert genauso.
Hinweis: Dieses Script stammt aus 2009 und nutzt iptables auf einem Debian mit Kernel 2.4. Die Konzepte sind zeitlos, aber die Umsetzung ist veraltet. Heute nimmt man nftables statt iptables. Trotzdem: Wer versteht was hier passiert, versteht auch nftables.
Das Setup
Dedizierte Firewall-Maschine mit drei Netzwerkkarten. Ein Interface zum Internet (PPPoE), zwei für interne Netze mit unterschiedlichen Berechtigungen. Default-Policy auf allen Chains: DROP. Alles was nicht explizit erlaubt ist, wird verworfen.
Eigene Chains für sauberes Logging. Jedes verworfene Paket wird mit Prefix geloggt bevor es gedroppt wird:
# MY_REJECT: Protokollieren und zurückweisen
iptables -N MY_REJECT
iptables -A MY_REJECT -p tcp -m limit --limit 7200/h -j LOG --log-prefix "REJECT TCP "
iptables -A MY_REJECT -p tcp -j REJECT --reject-with tcp-reset
iptables -A MY_REJECT -p udp -m limit --limit 7200/h -j LOG --log-prefix "REJECT UDP "
iptables -A MY_REJECT -p udp -j REJECT --reject-with icmp-port-unreachable
# MY_DROP: Portscans stillschweigend verwerfen
iptables -N MY_DROP
iptables -A MY_DROP -m limit --limit 7200/h -j LOG --log-prefix "PORTSCAN DROP "
iptables -A MY_DROP -j DROP
Stealth Scan Detection
Ungültige TCP-Flag-Kombinationen erkennen und verwerfen. Kein normaler Client setzt SYN+FIN gleichzeitig oder schickt ein Paket ohne Flags:
# Keine Flags gesetzt
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j MY_DROP
# SYN und FIN gleichzeitig
iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j MY_DROP
# SYN und RST gleichzeitig
iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j MY_DROP
# FIN ohne ACK
iptables -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j MY_DROP
Connection Tracking und NAT
Stateful Firewall: Bestehende und zugehörige Verbindungen durchlassen, neue nur aus dem internen Netz erlauben. NAT per MASQUERADE für den Internetzugang:
# Loopback erlauben
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Ausgehend: Alles erlauben
iptables -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Forwarding: Neue Verbindungen nur von innen
iptables -A FORWARD -i ! ppp0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# NAT für interne Netze
iptables -t nat -A POSTROUTING -o ppp0 -s 192.168.0.0/24 -j MASQUERADE
Traffic Shaping mit tc
Mit tc (traffic control) und iptables -t mangle lässt sich die Bandbreite pro Client oder Netz begrenzen. iptables markiert die Pakete, tc ordnet sie in Queues ein:
# HTB Queueing Discipline auf dem internen Interface
tc qdisc add dev eth2 root handle 1:0 htb default 10
tc class add dev eth2 parent 1:0 classid 1:1 htb rate 150kbit ceil 250kbit
tc filter add dev eth2 parent 1: prio 0 protocol ip handle 1 fw flowid 1:1
# Pakete per iptables markieren
iptables -t mangle -A FORWARD -s 192.168.100.0/24 -j MARK --set-mark 1
Kernel-Hardening
Am Ende des Scripts werden Kernel-Parameter gesetzt die über die Firewall hinausgehen:
# SYN-Cookies gegen SYN-Flood
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
# Source-Routing deaktivieren
for i in /proc/sys/net/ipv4/conf/*; do echo 0 > $i/accept_source_route; done
# Redirects ignorieren
for i in /proc/sys/net/ipv4/conf/*; do echo 0 > $i/accept_redirects; done
# Martian-Pakete loggen
for i in /proc/sys/net/ipv4/conf/*; do echo 1 > $i/log_martians; done
# ICMP-Ping ignorieren
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
# TCP-FIN-Timeout gegen DoS
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
Die Konzepte aus diesem Script gelten unverändert: Default DROP, Stateful Tracking, Custom Chains für Logging, Stealth Scan Detection, Kernel-Hardening. Nur die Syntax hat sich geändert. Wer heute eine Linux-Firewall baut, nimmt nft statt iptables und erspart sich die Modprobe-Zeilen. Für IPv6 braucht man eine eigene Regelkette, damals mit ip6tables, heute in nftables integriert.
Meine IPv6-Reise begann 2003. Damals kam IPv6 vom eigenen ISP nicht in Frage, also ging es über einen Tunnelbroker. SixXS stellte kostenlos IPv6-Tunnel bereit und vergab auf Antrag sogar ein komplettes /48 Netz. SixXS wurde 2017 eingestellt, aber die Grundlagen von IPv6 haben sich nicht geändert. Heute liefern die meisten ISPs IPv6 nativ aus.
Adressformat
IPv6-Adressen sind 128 Bit lang. Das sind 2128 mögliche Adressen, ausgeschrieben: 340.282.366.920.938.463.463.374.607.431.768.211.456. Geschrieben werden sie hexadezimal, jeweils zwei Bytes durch Doppelpunkt getrennt:
2a01:0198:0200:0945:0000:0000:0000:0002
Führende Nullen darf man weglassen. Ein zusammenhängender Block aus Nullen kann einmal durch :: ersetzt werden:
# Führende Nullen weg
2a01:198:200:945:0:0:0:2
# Ein Nullblock durch :: ersetzt
2a01:198:200:945::2
# Als Netz
2a01:198:200:945::/64
Wichtige Adressen
::1 ist der Localhost. fe80::/10 sind Link-Local-Adressen, die jedes Interface automatisch bekommt. ff02::1 erreicht alle Hosts im lokalen Netz, ff02::2 alle Router. 2001:db8::/32 ist für Dokumentation reserviert.
# Alle Hosts im lokalen Netz anpingen
ping6 -I eth0 ff02::1
# Alle Router im lokalen Netz anpingen
ping6 -I eth0 ff02::2
Adressbildung und EUI-64
Link-Local-Adressen bildet der Host selbst aus dem Prefix fe80 und der MAC-Adresse im EUI-64 Format. Die MAC-Adresse hat 48 Bit, EUI-64 erweitert sie auf 64 Bit. Zusammen mit dem 64-Bit-Prefix ergibt das die vollen 128 Bit. IPv6 ist damit bereits auf 64-Bit-MAC-Adressen vorbereitet, falls die 48-Bit-Adressen irgendwann knapp werden. Duplicate Address Detection (DAD) stellt sicher, dass keine Adresse doppelt vergeben wird.
Autokonfiguration mit radvd
ARP wurde durch Neighbor Discovery (ND) ersetzt. IPv6 ist auf Autokonfiguration ausgelegt. Ein Router Advertisement Daemon (radvd) teilt allen Hosts im Netz das IPv6-Prefix mit. Die Hosts bilden sich ihre Adresse selbst. Kein DHCP nötig für die Grundkonfiguration.
Nach dem Start von radvd holen sich alle Hosts im Netz automatisch eine Adresse aus dem Prefix. Prüfen mit:
ip addr show eth0
Kein NAT mehr
Mit IPv6 ist NAT nicht mehr nötig. Jedes Gerät bekommt eine eigene öffentliche Adresse. Das bedeutet aber auch: Jedes Gerät ist direkt aus dem Internet erreichbar. Eine Firewall ist Pflicht. IPv6 hat einen eigenen Paketfilter, ip6tables statt iptables. Wer iptables kennt, kann auch ip6tables.
Hurricane Electric bietet eine kostenlose IPv6-Zertifizierung an. Man arbeitet sich durch verschiedene Levels, vom ersten Tunnel bis zum voll IPv6-fähigen Mailserver. Am Ende gibt es ein T-Shirt.