Bis vor kurzem habe ich auf einem MacBook mit OS X/macOS gearbeitet, jetzt benutze ich ein Zenbook von Asus mit Ubuntu Budgie 17.04. Das funktioniert eigentlich ganz ordentlich, ohne dass ich bislang größere Dinge anpassen musste. Eine Sache treibt mich (und andere) allerdings in den Wahnsinn, nämlich Probleme mit der Stabilität der WLAN-Verbindung.

Seit ich mit Linux arbeite, funktioniert die WLAN-Verbindung im Büro nicht mehr zuverlässig, sondern bin ich dort immer mal wieder für ein paar Sekunden offline. Das kann einmal in der Stunde passieren oder alle zwei Minuten und ist ärgerlich, wenn ich gerade in einer Videokonferenz bin. Das ging mir so auf die Nerven, dass ich mir das im Detail angesehen habe.

Ein Blick ins Syslog

Zunächst lohnt meistens ein Blick in das Syslog (/var/log/syslog). Es zeigt, dass immer dann, wenn ich mal wieder offline bin, mein Rechner sich kurz vom WLAN verabschiedet hat und dann gleich wieder neu verbindet. Das sieht im Log dann ungefähr so aus:

Jul 28 17:36:19 tims-zenbook kernel: [31680.349201] wlp2s0: disconnect from AP de:9f:db:1b:54:dd for new auth to dc:9f:db:1c:54:dd

Meine WLAN-Verbindung wechselt also zwischen verschiedenen Access Points und bei jedem dieser Wechsel bricht die Verbindung für einige Sekunden ab. Das nennt man WLAN-Roaming. Ich arbeite in einem Laden, der sein WLAN über mehrere Access Points verbreitet, die dieselbe SSID benutzen. Mein Rechner sollte sich mit dem an seinem Standort stärksten Access Point verbinden, und verbunden bleiben. WLAN-Roaming ist eigentlich gut, damit ein Rechner sich automatisch mit einem neuen Access Point verbinden kann, wenn es sein muss. Mein Rechner übertreibt das offenbar.

Ich könnte jetzt einfach WLAN-Roaming deaktivieren, indem ich in den Netzwerk-Einstellungen die MAC-Adresse eines Access Points vorgebe, mit dem sich mein Rechner verbinden muss. Dann würde er aber, sollte ich mal durch das Büro springen, keine automatische Verbindung mehr zu einem anderen Access Point aufbauen, das will ich auch nicht.

wpa_supplicant im Detail

Ich wollte der Sache weiter auf den Grund gehen und habe mir einen Arbeitstag lang Debug-Meldungen derjenigen Linux-Komponente ausgeben lassen, die für WLAN-Verbindungen zuständig ist. Das ist in Ubuntu wpa_supplicant, ein im Hintergrund laufender Prozess, der sich darum kümmert, WLAN-Verbindungen aufzubauen, zu aktualisieren und eben auch WLAN-Roaming zu organisieren. Wie man an Debug-Meldungen von wpa_supplicant herankommt, habe ich hier gelesen.

Nach einem Tag hatte ich eine lange Liste von Debug-Meldungen, aus der ich zuerst gelernt habe, dass wpa_supplicant alle zwei Minuten alle verfügbaren WLAN-Netzwerke scannt. Man nennt das Hintergrund-Scan und wpa_supplicant macht den, damit Ubuntu mir immer eine einigermaßen aktuelle Liste verfügbarer WLAN-Netzwerke anzeigen kann, und um WLAN-Roaming durchführen zu können.

Wenn der Scan für das Netzwerk, mit dem mein Rechner verbunden ist, mehrere Access Points ergeben hat, entscheidet wpa_supplicant im Anschluss (also alle zwei Minuten), ob es die aktive WLAN-Verbindung zu einem anderem Access Point übertragen will. Bei mir sieht das in den Debug-Meldungen häufig so aus:

Aug  1 11:42:43 tims-zenbook wpa_supplicant[1333]: wlp2s0: Considering within-ESS reassociation
Aug  1 11:42:43 tims-zenbook wpa_supplicant[1333]: wlp2s0: Current BSS: dc:9f:db:1c:54:dd level=-70 snr=19 est_throughput=54000
Aug  1 11:42:43 tims-zenbook wpa_supplicant[1333]: wlp2s0: Selected BSS: de:9f:db:1b:54:dd level=-73 snr=19 est_throughput=54000
Aug  1 11:42:43 tims-zenbook wpa_supplicant[1333]: wlp2s0: Skip roam - Current BSS has better signal level

Das ist eine gute Debug-Meldung, weil sie sagt, dass der Access Point, mit dem mein Rechner derzeit verbunden ist (der „Current BSS“), ein stärkeres Signal liefert als der alternativ in Frage kommende Access Point (der „Selected BSS“; in Wirklichkeit gibt es noch weitere alternative Access Points, von denen sich wpa_supplicant bereits den besten potentiellen Access Point ausgesucht hat). Der Wert, der für level angegeben wird (das dürfte ein RSSI-Wert sein), ist besser, wenn er größer ist (-70 ist also besser als -73), ebenso bei dem Wert für est_throughput.

Leider sehe ich aber auch solche Debug-Meldungen, und zwar 42 Stück an einem Tag:

Aug  1 09:24:43 tims-zenbook wpa_supplicant[1333]: wlp2s0: Current BSS: dc:9f:db:1c:54:ce level=-71 snr=18 est_throughput=48000
Aug  1 09:24:43 tims-zenbook wpa_supplicant[1333]: wlp2s0: Selected BSS: dc:9f:db:1c:54:dd level=-63 snr=26 est_throughput=54000
Aug  1 09:24:43 tims-zenbook wpa_supplicant[1333]: wlp2s0: Allow reassociation - selected BSS has better estimated throughput

In der Theorie sollte so ein Szenario nicht vorkommen, wenn ich meinen Rechner nicht bewegt habe. wpa_supplicant sagt hier, dass der Access Point, mit dem mein Rechner verbunden ist, ein schwächeres Signal liefert als ein anderer verfügbarer Access Point. Jedes Mal, wenn das passiert, verbindet wpa_supplicant meinen Rechner neu und meine Internetverbindung verschwindet für ein paar Sekunden.

Da das zum Teil alle zwei Minuten passiert, schwankt offenbar die Qualität der Funkverbindung zwischen meinem Rechner und den Access Points. Das kann mehrere Gründe haben, etwa eine sehr sensible WLAN-Antenne in meinem Rechner, oder ein Problem mit dem WLAN-Treiber, oder Störsignale, die die Signalübertragung beeinträchtigen. Ich tippe auf Letzteres, weil die Schwankungen zu bestimmten Tageszeiten häufiger auftreten als zu anderen Zeiten.

Quelltext-Bastelei

Ich kann wpa_supplicant in einem Parameter mitteilen, den Hintergrund-Scan nicht so häufig durchzuführen, aber das würde mein Problem nicht lösen. Ich möchte wpa_supplicant sagen, dass es beim WLAN-Roaming etwas behutsamer vorgehen soll. Dafür gibt es, sagt das Internet, keine Parameter, die ich an wpa_supplicant übergeben kann, und, sage ich, auch keine Einstellungsoption im Netzwerk-Manager von Ubuntu Budgie.

Das Tolle an Linux ist, dass ich für so gut wie alle Software-Komponenten den Quelltext ansehen und verändern kann. Das habe ich als Nächstes getan. In der Aktualisierungsverwaltung von Ubuntu habe ich dafür eingestellt, dass auch Quelltext von Softwarepaketen herunterladbar sein soll. Dann konnte ich über sudo apt-get source wpasupplicant den Quelltext von wpa_supplicant in mein aktuelles Arbeitsverzeichnis herunterladen. Aus dem Archiv der Mailing-Liste von wpa_supplicant habe ich herausgefunden, dass die Entscheidung, ob nach einem Scan ein Wechsel der Access Points stattfindet, in der Funktion wpa_supplicant_need_to_roam getroffen wird, die sich in der Datei events.c befindet.

Dort wird diese Entscheidung anhand von zwei Prüfungen getroffen. Zunächst wird geprüft, ob der Wert est_throughput der beiden Access Points sich um mindestens den Wert 5000 unterscheidet:

if (selected->est_throughput > current_bss->est_throughput + 5000) {
    wpa_dbg(wpa_s, MSG_DEBUG, "Allow reassociation - selected BSS has better estimated throughput"); 
    return 1; 
}

Wenn das nicht der Fall wird, kommt eine zweite Prüfung ins Spiel, bei der die Signalstärken der beiden Access Points verglichen werden und ein je nach Signalstärke unterschiedlich großer Unterschied erreicht sein muss, damit ein Wechsel der Access Points stattfindet:

min_diff = 2; 
if (current_bss->level < 0) { 
    if (current_bss->level < -85) min_diff = 1; 
    else if (current_bss->level < -80) min_diff = 2; 
    else if (current_bss->level < -75) min_diff = 3; 
    else if (current_bss->level < -70) min_diff = 4; 
    else min_diff = 5; 
} 
if (abs(current_bss->level - selected->level) < min_diff) { 
    wpa_dbg(wpa_s, MSG_DEBUG, "Skip roam - too small difference " "in signal level"); 
    return 0; 
}
return 1;

Return 1 bedeutet hier jeweils, dass ein Wechsel zwischen den Access Points stattfinden soll, return 0 bedeutet, dass kein Wechsel stattfinden soll.

Die Grenzwerte für einen Wechsel zwischen den Access Points, die hartkodiert im Quelltext stehen, beruhen übrigens auf einer Schätzung der Entwickler von wpa_supplicant. Andere Betriebssysteme verwenden andere Grenzwerte, bei OS X/macOS wird Roaming etwa erst ausgeführt, wenn die Signalstärke des aktuellen Access Point -75 unterschreitet.

Hier könnte man einen Algorithmus implementieren, der sich merkt, wenn es in der Vergangenheit immer wieder kurzfristige Wechsel zwischen zwei Access Points gegeben hat, und dieses Verhalten vermeidet, oder man ändert einfach die hartkodierten Grenzwerte im Quelltext.

Meine Korrekturen

Genau das habe ich gemacht und die Grenzwerte für beide Prüfungen mit einem Texteditor im Quelltext geändert. Das sieht grob so aus:

if (selected->est_throughput > current_bss->est_throughput + 10000) {
    wpa_dbg(wpa_s, MSG_DEBUG, "Allow reassociation - selected BSS has better estimated throughput"); 
    return 1; 
}

Und so:

min_diff = 4; 
if (current_bss->level < 0) { 
    if (current_bss->level < -85) min_diff = 2; 
    else if (current_bss->level < -80) min_diff = 4; 
    else if (current_bss->level < -75) min_diff = 6; 
    else if (current_bss->level < -70) min_diff = 8; 
    else min_diff = 10; 
} 
if (abs(current_bss->level - selected->level) < min_diff) { 
    wpa_dbg(wpa_s, MSG_DEBUG, "Skip roam - too small difference " "in signal level"); 
    return 0; 
}
return 1;

Der Nachteil eines solchen Patches ist, dass ich ihn nach jedem Update von wpa_supplicant erneut anwenden muss, aber das nehme ich in Kauf. Den Patch habe ich hier zum Runterladen bereitgestellt.

Kompilieren und Fertig

Jetzt muss ich wpa_supplicant nur noch neu kompilieren und die ausführbare Datei austauschen. Das Kompilieren ist im Prinzip einfach, indem ich im Stammverzeichnis des Quelltextes make ausführe. Vorher müssen aber noch die für das Kompilieren erforderlichen Software-Pakete installiert werden. Das müsste über sudo apt-get build-dep wpasupplicant funktionieren, ich habe sie aber manuell nach dieser Anleitung installiert.

wpa_supplicant benötigt eine Konfigurationsdatei, um erfolgreich kompiliert werden zu können, die .config heißen muss. Im Quelltext gibt es eine Beispielsdatei defconfig dafür, die sich für das Kompilieren unter Ubuntu aber nicht eignet. Stattdessen verwende ich die Konfigurationsdatei debian/config/wpasupplicant/linux, die sich im kompriminierten Verzeichnis wpa_2.4-0ubuntu9.debian.tar.xz innerhalb des Quelltextes befindet. Diese Datei muss als .config in das Stammverzeichnis des Quelltextes kopiert werden, dann kann ich über make eine neue Version von wpa_supplicant kompilieren.

Bevor ich nun die neue Version von wpa_supplicant in das Verzeichnis /sbin kopiere, beende ich den Netzwerk-Manager über sudo service network-manager stop und den laufenden wpa_supplicant-Prozess über killall wpa_supplicant. Ich sichere die ursprüngliche Version von wpa_supplicant aus dem Verzeichnis /sbin und kopiere dann die neue Version von wpa_supplicant nach /sbin. Ein Aufruf von sudo service network-manager start und schon läuft meine Version von wpa_supplicant mit angepassten Grenzwerten für das WLAN-Roaming.