BT-NAP reloaded

Kurz nachdem ich meinen letzten Beitrag über BT-NAP veröffentlichte, brachte Google das Android 4.2 Update heraus, mit dem es dann unter Umständen nicht mehr funktionierte. Was passiert ist und wie man es dann doch wieder zum Laufen bekommt, erkläre ich dann mal eben hier.

Android 4.2 ist zugegebenermaßen nicht mehr sonderlich neu und den kleinen Fix, den ich im Folgenden zeige, benutze ich selbst nun auch schon seit ungefähr einem Monat nach dem Erscheinen.
Es sei somit an dieser Stelle nachgeholt, das jetzt auch einmal schriftlich fest zu halten.

Aber der Reihe nach. Was hat sich geändert, und wer ist betroffen? Betroffen dürften die wenigsten Nutzer sein, die überhaupt Ethernet über Bluetooth auf Android verwenden. Jedoch hat sich Google einen Patzer erlaubt, bei dem ich mir nicht ganz sicher bin, ob das jetzt ein Bug, oder ein Feature sein soll.
Schmählich sei zu erwähnen, dass Google im Zuge des Updates auf Android 4.2 einen Bluetooth Stack aus eigener Feder eingebaut hat, den sie in Zusammenarbeit mit Broadcom entwickelten.
Wie immer, wenn diese Firma ihre Finger mit im Spiel hat, kann man sich darauf verlassen, dass am Ende irgendwas nicht so funktioniert, wie es sollte. Geht es um Software, so liegt es meist daran, dass Standards nicht eingehalten wurden.

Auch diesmal enttäuschte Broadcom nicht und so funktionierte mein schönes Bluetooth Netzwerk nicht mehr, sobald Android 4.2 aufgespielt war.

Nutze ich hingegen ein anderes Smartphone als Bluetooth Access Point, ging alles wie bisher. Das legt ja zumindest erst mal die Vermutung nahe, dass ich bei meinem Setup irgendwas falsch gemacht hatte.
Von meinem Server bekam ich zwar noch eine IP Adresse, aber das war dann auch schon alles. Versuchte ich hingegen irgend eine Seite aufzurufen, oder auch nur nach Emails zu laden – nichts.
Ich überprüfte als nächstes die per DHCP übermittelte Netzwerkkonfiguration auf den Androiden. IP Adresse, DNS, Default Gateway und auch das restliche Routing waren völlig in Ordnung.
Von der Netzwerkkonfiguration auf IP Ebene her hätte es funktionieren müssen. Das tat es aber nicht. Ich bekam DNS Requests aufgelöst, aber schon ein Ping schlug fehl, sogar im eigenen Netzwerk. Der einzige Rechner, den ich völlig normal erreichen konnte, war der Server welcher als Endpunkt für die Bluetooth Verbindung diente.

Erst ein Dump der Bluetooth Schnittstelle auf dem Server brachte die traurige Wahrheit ans Licht:

Kein Einziges Paket, welches nicht die Ethernet MAC Adresse des Access Points als Zieladresse hatte, verließ überhaupt die Androiden!

Bis heute ist mir nicht klar, ob es sich dabei um Absicht (und damit wohl um eins der missglücktesten Sicherheitsfeatures, welche ich je gesehen habe) handelt, oder ob sich da wirklich ein unglaublich dämlicher Bug durch die Qualitätssicherung geschlichen hat.

IP Routing basiert unter anderem auf der Vorraussetzung, dass jeder Host im gleichen Subnetz direkt über seine MAC Adresse angesprochen werden kann.
Android 4.2 hingegen schickt zwar einen ARP (Adress Resolution Protocol) Request, wenn zu einer IP Adresse im Subnetz die MAC Adresse nicht bekannt ist, kommuniziert aber nur mit der MAC Adresse des Bluetooth Access Points, oder der Broadcastadresse (zum Glück).
Um es verständlicher auszudrücken: Es redet nur mit dem einen Host mit dem die Ethernetverbindung über Bluetooth hergestellt wurde, oder mit dem ganzen Subnetz.

Man könnte nun denken, dass dies ein gewaltiges Problem ist, welches doch schon etwas mehr Lärm im digitalen Raum hätte erzeugen müssen. Hier liegt auch der Grund, warum ich mir eben nicht sicher bin, ob es sich um Absicht handelt.

Zum Surfen im Internet reicht das aus!

Zumindest wenn eine Voraussetzung gegeben ist. Der Host, welcher als Bluetooth Endpunkt für die Verbindungen der Android Geräte dient, muss gleichzeitig Router des Subnetzwerkes, DNS und DHCP Server sein.
Verbindet man sich mit einem anderen Smartphone, ist genau das der Fall.
Auch wenn man noch ein altes Modem verwendet, welches keine Routerfunktion übernimmt, so kann dieses Setup funktionieren, wenn der Host der die Internetverbindung hält, genau alle oben genannten Voraussetzungen erfüllt.

Eine ähnliche Funktion gibt es auch für bessere WLAN Access Points. Sie garantiert, dass sich die Teilnehmer in einem WLAN nicht gegenseitig attackieren können, weil sie jegliche Kommunikation zwischen den Teilnehmern unterbindet. Diese Funktion wird gerne Client Isolation genannt.

Nimmt man nun einmal an, dass es sich hierbei um Absicht handelte, so drängt sich doch gleich die Frage auf: Welchen Nutzen sollte Client Isolation in einem Heimnetzwerk haben? Ist es nicht gerade Sinn und Zweck eines Heimnetzwerks, dass lokale Geräte auch untereinander kommunizieren können und nicht nur jedes einzelne Gerät mit dem Internet?
Eine Antwort auf diese Frage würde ich jedenfalls gerne mal von Google irgendwo lesen, allerdings bezweifle ich inzwischen, dass sich Google zu dem Problem jemals noch äußert.
Derzeit kann ein über Bluetooth eingebundenes Gerät keinen anderen Host erreichen, außer dem Bluetooth AP. Damit fallen sämtliche Verbindungen zu Anderen Teilnehmern, wie z.B. Netzwerkfreigaben aus.

Also was kann man jetzt tun, um ein Android 4.2 Gerät doch noch über Bluetooth in ein Heimnetzwerk einzubinden?

Da es nur in Custom ROMs möglich ist, an so tiefliegenden Teilen von Android etwas zu ändern, wie es nötig wäre, um das Problem direkt auf den Endgeräten zu lösen, muss man eben das Netzwerk so anpassen, dass es dem Androiden vorgaukelt, sich genau in Situation zu befinden, die erforderlich ist.

Um das zu erreichen, muss man sich ein bisschen mit dem ARP (Adress Resolution Protocol) beschäftigen. Heutzutage ist es kaum noch bekannt und das ist ziemlich verwunderlich, weil IP ohne ARP überhaupt nicht funktionieren würde. Offensichtlich funktioniert es zu Problemlos, als das sich jemand damit beschäftigen müsste.

Hier ein paar Minimalgrundlagen: Jeder Teilnehmer im IP führt eine Tabelle, in der die Ethernet MAC Adresse für eine IP Adresse im Subnetz gespeichert werden kann. Soll eine IP Adresse erreicht werden, die nicht im Subnetz liegt, wird das Paket einfach an den für das Zielsubnetz zuständigen Router übermittelt.
Für das Internet ist dies für gewöhnlich der sogenannte Default Gateway.
Was ist aber mit den Hosts, die sich im gleichen Subnetz befinden? Diese sollten im Normalfall direkt auf Ethernet Ebene erreichbar sein. Sei dies nun Netzwerkkabel, WLAN oder eben auch Ethernet über Bluetooth.
Ist einem Gerät im Subnetz die Ethernet MAC Adresse einer IP Zieladresse nicht bekannt, so fragt es über das ARP einfach nach.

Hier ist XYZ, wie ist die Ethernet Adresse zur IP Adresse ABC?

Da die Zieladresse nicht bekannt ist, wird diese Anfrage einfach über die Broadcastadresse an alle Teilnehmer im Subnetz gestellt. Jeder Teilnehmer, der die Antwort kennt, ist berechtigt, diese zu übermitteln. Dies geschieht normalerweise jetzt mit dem Fragestellenden als Zieladresse.
Nun wo diesem die MAC Adresse zur IP Adresse bekannt ist, kann der Teilnehmer seine eigentlichen IP Nutzdaten übermitteln.

Dieser Vorgang funktioniert nun durch Googles Verschlimmbesserung nur noch mit dem Bluetooth Access Point als Zielgerät.
Da ich daran nichts ändern kann, baue ich etwas ungewöhnliches: Einen Router für das eigene Subnetz!

Ähnliche Lösungen gibt es schon für komplett getrennte Teile des gleichen Subnetzes. Diese können durch einen sogenannten ARP Proxy verbunden werden. Der ARP Proxy antwortet dazu einfach auf alle Anfragen nach einem Host in einem anderen Teil des Subnetzes mit seiner eigenen MAC Adresse. Wird ihm dann ein IP Paket für diesen zugestellt, übermittelt er dieses dann stellvertretend für den Absender.
Ein ARP Proxy hilft aber nicht dabei Pakete zu vermitteln, die gar nicht über IP transportiert werden. So funktioniert z.B. DHCP nicht durch eine Verbindung über einen ARP Proxy hinweg, wenn dort nicht auch ein DHCP Relay installiert ist.

Diese Lösung lässt sich in diesem Fall aber nicht anwenden, weil das Netz in Richtung des Androiden völlig normal funktioniert. Jeder Teilnehmer im Subnetz kann dem Androiden ein IP Paket zustellen. Dieser Antwortet nur einfach nicht.

Damit haben wir ein halbes Problem, für das sich auch eine passende halbe Lösung finden lässt. Der Eingriff in das lokale Netz soll so gering wie nötig ausfallen.

Der Einfachheit halber erkläre ich die Lösung an den fünf kleinen Zeilen, die dafür nötig sind.

Zunächst brauchen wir das Paket ebtables. Es ähnelt dem viel besser bekannten Paket iptables in Bedienung und Funktion. Während iptables genutzt wird, um Pakete auf IP Ebene zu manipulieren und damit z.B. das Routing zu beeinflussen, verändert ebtables Ethernetpakete, die eine Bridge durchlaufen.

Die Installation beispielhaft für Debian und Ubuntu:

sudo apt-get install ebtables

Die erste Zeile:

sudo ebtables -t nat -A PREROUTING -i bnep+ -p arp -j dnat --to-destination 'FF:FF:FF:FF:FF:FF'

In jedem ARP Paket, welches die Bridge über eine Schnittstelle, deren Name mit bnep beginnt (dazu dient das +) erreicht, wird noch vor dem Routing die Zieladresse auf die Ethernet Broadcastadresse (FF:…) gesetzt.

Da die Anfragen des Androiden hinter bnepX ohnehin an die Broadcastadresse gehen, betrifft dies effektiv nur die Antworten auf ARP Anfragen (ARP Reply), die der Androide gibt. Wir erinnern uns: Die Antwort erfolgt normalerweise Zielgerichtet an den Fragestellenden.

Die zweite Zeile:

sudo ebtables -t nat -A POSTROUTING -p ARP -o bnep+ -j snat --to-src YO:UR:MA:C0:HE:RE --snat-arp --snat-target ACCEPT

Hier soll bei jedem ARP Paket, welches die Bridge über bnepX verlässt, der Absender auf die MAC Adresse des Bluetooth Interfaces in der Bridge gesetzt werden. YO:UR:MA:C0:HE:REist natürlich für die eigenen Bedürfnisse anzupassen. --snat-target ist ein Zusatz, der nicht nur den Absender des Ethernetpakets auf diese Adresse umschreibt, sondern auch das TARGET Feld der ARP Antwort.
Da ARP Anfragen stellvertretend für Andere Teilnehmer beantwortet werden dürfen, ist das TARGET nicht immer gleich dem Absender der Antwort und muss extra gesetzt werden.

Die dritte Zeile:

sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"

Dies beeinflusst, was die Kernel auf der Bridge mit IP Paketen machen soll, in der keine ihrer IP Adressen als Absender, oder Adressat vorkommt. Auf den meisten Systemen steht dieser Wert auf 0 was die Kernel dazu veranlasst, diese Pakete einfach zu verwerfen. Diese Änderung bewirkt, dass die Pakete stattdessen gemäß der eigenen Routingtabelle weitergeleitet werden.

Die vierte Zeile:

sudo sh -c "echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects"

Normalerweise sendet die Kernel automatisch einen ICMP Redirect, wenn sie der Meinung ist, dass der Absender eines IP Pakets den Adressaten auf einem günstigern Weg erreichen könnte. Da Android 4.2 aber keine Teilnehmer des eigenen Subnetztes mehr erreich kann, ist diese Annahme immer falsch und der Redirect muss abgestellt werden.

Und wie Spielt das jetzt zusammen?

Das erläutere ich jetzt mal anhand von zwei Beispielen:

1. Der Androide möchte selbst ein IP Paket verschicken.

Will der Androide einen Teilnehmer im Subnetz erreichen, wird er zunächst versuchen die MAC Adresse des Teilnehmers über einen ARP Request zu erfahren. Dieser geht ganz normal durch die Bridge und erreicht über die Broadcastadresse das komplette Subnetz.
Von irgendwo im Subnetz erreicht jetzt die dazu gehörige Antwort auch die Bridge. Diese setzt jetzt die MAC Adresse in Absender und TARGET Feld auf ihre eigene. Diese Änderung sieht niemand außer dem defekten Androiden selbst, so dass die anderen Teilnehmer nicht verwirrt werden.
Damit denkt der Androide, die IP Adresse würde der Bridge gehören und wird alle folgenden IP Pakete für diese IP Adresse der Bridge geben.
Die Bridge stellt aber in ihrem Routing Code fest, dass die Pakete gar nicht für sie sind und leitet sie an den tatsächlichen Empfänger weiter. Ein Redirect wurde manuell abgestellt und bleibt damit aus.
Für den Empfänger bleibt der Vorgang unbemerkt, der Absender auf IP Ebene ist weiterhin der Androide.

2. Ein anderer Teilnehmer im Subnetz möchte den Androiden erreichen.

Auch dieser versendet einen ARP Request. Der Request erreicht die Bridge und wird dort so geändert, als wäre die Anfrage von der Bridge selbst gekommen.
Der Androide Antwortet jetzt aber der Bridge direkt, so dass der ursprüngliche Teilnehmer die Antwort gar nicht erhalten würde – wenn wir nicht in der ersten Zeile befohlen hätten, dass alle über Bluetooth eingehenden Pakete, die die Bridge erhält, auf die Broadcastadresse umgelenkt werden sollen.
Damit erhält die Antwort also jeder Teilnehmer im Subnetz, das schließt auch denjenigen mit ein, der urpsrünglich den ARP Request gemacht hat.
Eine ARP Antwort an die Broadcastadresse zu schicken ist zwar unüblich, aber nach Standard zulässig und wird auch z.B. beim unsolicited ARP eingesetzt, um Änderungen aktiv durch das Netzwerk zu propagieren.
Auf diesem Umweg haben wir es jetzt geschafft, dass der Androide einen ARP Reply zu einem Teilnehmer schickt, mit dem er normalerweise gar nicht reden würde.
Zum Androiden hin ist der Weg im Netzwerk nicht gestört, die Pakete durchlaufen, nach Bekanntwerden seiner MAC Adresse, ganz normal die Bridge.

Logisch gesehen nehmen die Pakete jetzt zwei unterschiedliche Wege.

Vom Androiden ins restliche lokale Netzwerk werden sie auf der Bridge in Layer 3 gehievt und dort geroutet, auf dem Rückweg werden sie ganz normal auf Layer 2 zugestellt.
Eine Lösung auf Layer 3 (IP Routing) Basis hätte nicht funktioniert, da auf Layer 2 (Ethernet) nicht alle Teilnehmer in einem Subnetz ungehindert kommunizieren konnten, wie es für IP Voraussetzung gewesen wäre.
Diese Lösung hingegen behebt das Problem schon auf Layer 2, wo durch Manipulationen am ARP wieder eine für Layer 3 transparente Zustellung der Pakete erreicht werden konnte.