IoTA


Internet of Things Automation und das lernende Smart Home

Kreuzvalidierungsfail

Prinzipiell alles richtig gemacht - oder eben doch nicht

Validierung der Prognosegüte von Modellen des maschinellen Lernens über hochgradige Kreuzvalidierung ist der Standard für zuverlässige Aussagen. Wie man sich damit leicht selbst auf das Glatteis führen kann, davon erzählt diese Geschichte.

Hintergrund

Sommerschule für maschinelles Lernen unter Ressourcenbeschränkung

Der Sonderforschungsbereich 876 - Große Daten, Kleine Geräte, an der TU Dortmund führt regelmäßig Sommerschulen zum Thema Maschinelles Lernen unter Ressourcenbeschränkung durch. Ganz zum Forschungsgebiet des Sonderforschungsbereichs passend beschäftigen sich die Sommerschulen damit, wie die Analyse großer Daten, insbesondere auch auf eingebetteten Systemen und im Verbund mit sehr stark beschränkten Rechenressourcen, gelingen kann. Jede Sommerschule versucht dabei neben den eigentlichen Kursen auch einen Hands-On-Teil dazu anzubieten. Auch die innerhalb kürzester Zeit ausverkaufte Sommerschule 2017 war dabei keine Ausnahme.

Versuchsaufbau
Versuchsaufbau aus mehreren PhyNode-Boxen

Das Szenario war dazu an ein Problem aus der Warenlogistik angelegt: Wie finde ich im Regal meine Box wieder? Im Versuchsaufbau wurden zwei kleine Wände aus Warencontainern aufgebaut, wobei jeder Container mit einem im Projekt Ressourcen-effiziente und verteilte Plattformen zur integrativen Datenanalyse entwickelten PhyNode-Board versehen ist.

Aufgabe

Die PhyNodes sind stark ressourcenbeschränkte, autonome und weitestgehend energieautarke eingebettete Systeme mit einer Anzahl verschiedener Sensoren.

Die Aufgabe der Sommerschule bestand darin, das Lernen von Positionen von Boxen mit angebrachten PhyNodes für mehr als 20 Positionen als Labels zu realisieren. Später sollten die Boards selbst durch die Software auf den Boards ihre eigenen Positionen im Regal vorhersagen können. Die Teilnehmer erhielten dazu Trainingsdaten mit der bekannten Position. Die Boards nahmen im Verlauf verschiedene Positionen ein, d.h. ID des Boards und Position stimmten nicht immer überein. Die Messreihen zwischen dem Umstecken werden im Folgenden als “Experiment” bezeichnet.

Features

Verfügbare Features waren Zeit, Board ID, Temperatur, Luxwerte, Beschleunigungswerte X, Y, Z, RSSI zur Basistation, das Experiment und die Position als Label. Positionen bestanden aus zwei Ziffern mit der Bedeutung Spalte/Zeile, was für die Modellbildung aber keine Rolle spielte, da die Aufgabe zunächst als Klassifikationsproblem behandelt wurde. In Summe standen nach den ersten Experimenten mehr als 330.000 Messungen zur Verfügung.

Modellierung

Um einen ersten Eindruck von der Schwierigkeit des Problems zu bekommen (These: Das geht gar nicht) bietet es sich an beispielsweise eine schnelle Analyse mit RapidMiner auszuprobieren. Die Daten sind damit fix eingelesen und die wichtigsten Methoden mit ein paar Klicks verfügbar.

Der simple Ansatz ist dann: Daten einfach mit einem RandomForest mit Standardparametern (10 Bäume) gelernt, 10-fache Kreuzvalidierung und schauen, wo wir landen.

X-Validation
Ausschnitt des Vorgehens bei einer 10-fachen Kreuzvalidierung

Die Kreuzvalidierung, insbesondere in 10-facher Ausführung stellt dabei sicher, dass unter maximaler Ausnutzung der Trainingsdaten ein sicheres Bild über die zu erwartende Güte gewonnen wird. Die Daten werden dazu gleichmäßig in 10 Blöcke unterteilt, wobei in jedem Trainings-/Testlauf ein Block für das Testing zurückbehalten wird, während die übrigen 90 % zum Training des Verfahrens zur Verfügung stehen.

Confusion Matrix Random Forest
Ausschnitt der Confusion Matrix unter Einsatz eines Random Forest

Für den Random Forest liefert diese Validierung dann auch eine Klassifikationsgüte von über 99 %. Ziel erreicht, perfekte Positionsvorhersage mit einfache Mitteln ist möglich, wir können nach Hause gehen. Schade um die Aufgabe, für eine Sommerschule war das dann wohl doch zu einfach. Naja, es blieb immerhin noch das Problem, das trainierte Modell auch auf die Ultra-Low-Power-Plattformen zu bringen.

Das Problem

Mehr als 99 %, und das ohne Aufwand. Hinterlässt aber irgendwie doch einen leichten Beigeschmack. Vielleicht lohnt es sich doch einmal etwas näher hinter die Kulissen zu schauen. Zur einfachen Interpretierbarkeit zeigt die folgende Abbildung den Ausschnitt aus einem einfachen Regelmodell anstelle des Random Forest.

RuleModel
Ausschnitt eines einfachen Regel-Modells

So einfach sieht das Modell aber eigentlich gar nicht aus. Im Gegenteil eher überkomplex und overfittet. Auffällig ist die Einbeziehung der Beschleunigungs-Werte (Acc_x …). Welche Rolle sollten diese im statischen Fall, d.h. Boards bewegen sich nicht, spielen? Eigentlich sollte das Modell unwichtige Attribute selbst aussortieren.

Es scheint als ob die Beschleunigungs-Werte einen deutlichen Bezug zur Position und zum Experiment haben, quasi wie ein Fingerabdruck.

Experiment Vs AccelZ Vs Board
Plot von zwei Experimenten (Zeitachse) gegenüber Z-Beschleunigung gegenüber zwei Boards (ID, Farbe)

Der Plot zeigt beispielhaft den Z-Wert der Beschleunigungsmesser für zwei Boards (rot und blau) für zwei Experimente (entlang der Zeitachse mit kurzer Unterbrechung zum links und rechts). Die Werte sind im Mittel stabil, deutlich voneinander trennbar, und unterscheiden sich zwischen den Boards. Und nach dem Umstecken ändert sich der Z-Wert an der neuen Position. Was passiert hier? Sind die Sensoren so empfindlich, dass die Erdbeschleunigung unterschiedliche Höhen sichtbar macht?

Wohl eher nicht, eine viel ernüchternde Erklärung: Die Boards scheinen nach dem Umstecken immer leicht schief in den Boxen zu landen, was die Unterschiede in den Beschleunigungswerten ausmacht und die eindeutige Identifikation eines Boards an einer bestimmten Position ermöglicht. Leider sind diese Werte nicht reproduzierbar, damit nur Rauschen und nicht für ein in der Realität anwendbares Modell geeignet. Sobald Daten eines Experiments für die Aufteilung der Kreuzvalidierung für ein Board sowohl im Trainings- als auch im Testdatensatz vorkommen erzeugen diese den Fingerabdruck dieses einen Boards. Eigentlich sollten aber die Messdaten unabhängig vom Board Rückschlüsse auf die Position erlauben. Die Beschleunigungssensoren codieren damit (fast eindeutig) das Label (Information Leakage).

Und die Situation wird noch schlimmer: In den Daten sind 9 Experimente enthalten, d.h. 9 mal Umstecken der PhyNode-Boards an neue Positionen. In einer 10-fachen stratifizierten Kreuzvalidierung stecken damit immer sowohl in Trainings- als auch Testdaten Anteile aus allen Experimenten.

Wird die Kreuzvalidierung dagegen auf Batchweise umgestellt mit dem Experiment als Attribut für den Train-/Testsplit sehen die Ergebnisse ganz anders aus. Jetzt wird immer jeweils ein vollständiges Experiment für den Test zurückgehalten während die anderen 8 Experimente dem Training dienen. Schon sind es nur noch 10 % Genauigkeit, siehe dazu wieder einen Ausschnitt der Konfusionsmatrix für einen Random Forest mit derselben Parametrisierung wie zuvor.

Confusion Matrix mit angepasster Validierung
Confusion Matrix mit angepasster Validierung über vollständige Experimente

10 % bei mehr als 20 Labeln ist immer noch besser als raten, aber nicht beeindruckend. Immerhin ist damit zumindest die Basis für eine korrekte Validierung gelegt und der eigentliche Zyklus aus Modellwahl und Parametrisierung kann beginnen.

Was sich in dem Szenario dann letztendlich erreichen lässt haben die Kollegen anschließend in einer Publikation auf der Smart SysTech 2018 gezeigt. Mit viel Mühe sind damit immerhin wieder Klassifikationsgüten von über 80 % möglich.


Homeserver Teil 2

Die Software - Von KNX bis OpenHAB

Nachdem im vorherigen Beitrag zum Homeserver die Hardware kurz vorgestellt wurde geht es jetzt um die Installation der eigentlichen Steuerung. Voraussetzung ist ein lauffähiges Raspbian auf dem Pi.

KNX-Dämon

Standards, mehr oder weniger offen, für die Ansteuerung von Haustechnik gibt es viele. Bei uns kommt primär KNX zum Einsatz, welches den Vorteil hat ein offener Industriestandard zu sein und mit (hoffentlich) entsprechend langfristiger Unterstützung und herstellerunabhängiger Nutzbarkeit der Komponenten einhergeht. Kein Vendor-Lock-in mehr. Bislang scheint das für unser System auch hervorragend zu funktionieren.

Zur Ansteuerung der Komponenten benötigt es aber ein Gateway. Entweder eines der kommerziellen IP-Gateways oder wie hier über ein Hardwaremodul auf dem Raspberry Pi. Dieses benötigt allerdings noch einen Dämon zur Ansteuerung, der sowohl die Programmierung der Komponenten für ihre nativen Funktionen erlaubt als auch Zugriff für die Steuerzentrale für erweiterte Funktionen.

Dazu gibt es mit knxd eine freie Alternative, die sich auch problemlos auf dem Raspberry Pi einbinden lässt. Mit der zum aktuellen Zeitpunkt verfügbaren Version im Hauptentwicklungszweig, 0.14 (genau genommen der Commit 61a08e5fa5ce4cc6d47309bf80f9abc1fd792637, da sich Version 0.14 noch aktiv in der Entwicklung befand), gab es bislang keine Probleme, weder beim Programmieren der Geräte noch bei der Ansteuerung von Komponenten über den Homeserver.

Für den Raspberry Pi gibt es einen eigenen Abschnitt zur Konfiguration der Schnittstelle: https://github.com/knxd/knxd#adding-a-tpuart-serial-interface-to-the-raspberry-pi

Automatisierung mit OpenHAB

Visualisierung und Ansteuerung der Komponenten läuft dann über das freie OpenHAB-Projekt. Für KNX gibt es Alternativen, aber insbesondere durch die große Community, die Auswahl an Plugins und die Anbindung an unzählige weitere Standards (Jemand mit Tesla Modell S hier?) führt an Openhab kaum ein Weg vorbei.

Docker auf dem Raspberry Pi

Die Installation von OpenHAB ist mehr oder weniger aufwändig. Glücklicherweise existieren verschiedene Optionen auch für den Raspberry. Eine davon ist openHABian als eigene Distribution, die Raspbian als OS auf der SD-Karte ersetzt. Eine Alternative ist die Nutzung von Docker.

Docker ist eine sogenannte Containerisierungs-Lösung, die einzelne Software bis hin zu ganzen Betriebssystemen kapselt und (mehr oder weniger) unabhängig vom eigentlichen System ausführbar macht. Hauptvorteil ist damit der schnelle Wechsel zwischen verschiedenen Versionen und im Fehlerfall die Rückkehr auf die letzte funktionierende Version ohne sich mit Abhängigkeiten von anderen Paketen beschäftigen zu müssen.

Die Installation von Docker auf dem Raspberry funktioniert reibungslos über die Angabe von curl -sSL https://get.docker.com | sh auf der Kommandozeile, siehe den Artikel Docker comes to Raspberry Pi.

Fertige Images gibt es öffentlich verfügbar über den Dockerhub unter (https://hub.docker.com/r/openhab/openhab/). Eine mögliche Variante für den Raspberry wäre demnach

docker run \
        --name openhab \
        --net=host \
        --tty \
        -v /etc/localtime:/etc/localtime:ro \
        -v /etc/timezone:/etc/timezone:ro \
        -v openhab_addons:/openhab/addons \
        -v openhab_conf:/openhab/conf \
        -v openhab_userdata:/openhab/userdata \
        -d \
        --restart=always \
        openhab/openhab:2.3.0-armhf-debian

Die Option --restart=always sorgt dabei dafür, dass Openhab auch nach dem Reboot oder bei Abstürzen automatisch wieder gestartet wird. Die -v openhab...-Optionen machen Ordner außerhalb des Images, d.h. vom Raspberry, innerhalb des laufenden Openhab-Containers verfügbar. Dies ist insbesondere dann hilfreich wenn die Daten nicht auf der SD-Karte des Raspberrys landen sollen, z.B. aus Backup-Gründen oder um die Lebenszeit der SD-Karte durch weniger Schreibzyklen zu erhöhen. Aus denselben Gründen kann das gesamte Docker-Root, in dem Images und Container liegen, beispielsweise auf ein NAS verlagert werden. Dem Thema widmet sich der nächste Abschnitt.

Um auch den Docker-Daemon bei jedem Boot wieder zu starten (denn auch nur dann starten die Container wieder automatisch) muss der Service aktiviert werden: sudo systemctl enable docker

Einbinden eines NAS

Die SD-Karte im Raspberry Pi ist (meist) klein und mit der Begrenzung an Schreibzyklen auch anfällig für Datenverlust (wer hebt bei regelmäßigen Vollbackups des mühselig angelegten Systems die Hand?). Eine Möglichkeit ist Verzeichnisse mit großen Datenmengen oder Dateien mit regelmäßigen Schreibzyklen auf ein Network Attached Storage zu verlegen.

Im Wesentlichen bedeutet das hier eine Verlegung des Docker-Datenverzeichnisses und der OpenHAB-Konfiguratinsverzeichnisse. Dazu ist jeweils ein Eintrag in der /etc/fstab-Datei notwendig:

//<nas-ip>/docker /docker cifs user=<nasuser>,password=<naspasswort>,uid=root,gid=root,sec=ntlmv2,file_mode=0644,dir_mode=0755,nounix,sfu 0 0
//<nas-ip>/openhab /openhab cifs user=<nasuser>,password=<naspasswort>,uid=root,gid=root,sec=ntlmv2,file_mode=0700,dir_mode=0700,noperm,nounix 0 0

Zwingend notwendig sind natürlich die Angabe der korrekten IP-Adresse zum NAS, die Pfade zu den Ordnern im NAS und die Zugangsdaten eines dazu angelegten Users. Wer sein Passwort nicht in der fstab im Klartext ablegen möchte, kann dies auch in eine Credentials-Datei auslagern1.

Die übrigen Optionen waren für den Betrieb mit unserem NAS ebenfalls notwendig, z.B. um die Zugriffsrechte für Docker und den OpenHAB-Container passend zu setzen, bei der Einrichtung starten würde ich aber zunächst ohne und erst testen, ob der Zugriff auf das eigene NAS auch mit den Standardwerten direkt möglich ist.

Damit die Ordner nach einem Neustart des Raspberry Pi sicher verfügbar sind sollte in der Konfiguration dort per sudo raspi-config die Option Wait for network at boot aktiviert werden.

Um dem Docker-Daemon das neue Root für seine Daten bekannt zu geben ist diese in der Datei /etc/docker/daemon.json zu konfigurieren:

{
  "storage-driver": "devicemapper",
  "data-root": "/docker"
}

Der Storage-Driver legt fest, wie die Images und Container dort abgelegt werden. Devicemapper wird dabei explizit nicht für eine Produktiveinsatz empfohlen, da dieses unter anderem Probleme der Freigabe gelöschten Speicherplatzes hat. Leider war dies die einzig funktionierende Alternative (ymmv2) und da nicht ständig neue Container angelegt und gelöscht werden spielt der Speicherbedarf bislang (und hoffentlich auch zukünftig) keine Rolle.

Bedienoberfläche HABPanel

Der letzte (und aufwändigste, bzw. nie endende) Schritt ist dann die Konfiguration von OpenHAB und der Nutzeroberfläche. Dazu existieren verschiedene Varianten, besonders passend für den Touchscreen des Raspberry Pi ist HABPanel.

Zum Zugriff auf die Aktoren und Sensoren des KNX-Bus zu bekommen müssen diese zunächst in der jeweiligen Konfiguration definiert werden, siehe KNX-Binding.

Den sogenannten Items können dann direkt im Browser den passenden Schaltflächen zugeordnet werden.

Habpanel Startseite
Übersichtsseite in OpenHAB mit HABPanel als Oberfläche

Der Screenshot zeigt den Übersichtsbildschirm des Wandpanels. Neben den wichtigsten zentralen Messdaten wie Temperatur Außen/Wohnen, Feuchte und CO2-Gehalt wird per Wetterplugin auch die Wettervorhersage für die nächsten zwei Tagen angezeigt. Dazu kommt die Nachrüstung von seltener genutzter Funktionalität. So können per herkömmlichem Taster die Rollläden in Wohnzimmer und Küche nur gesamt verfahren werden, was für 90% der Fälle auch ausreicht. Mit dem Panel dagegen ist auch die Einzelsteuerung möglich.

Display auf dem Wandpanel

Die HABPanel-Oberfläche lässt sich auf jedem Gerät im lokalen Netzwerk anzeigen und nutzen. Gedacht ist sie allerdings für das Wandpanel aus dem Beitrag Homeserver - Die Hardware.

Die Anzeige übernehmen kann der Chromium-Browser per DISPLAY=:0 chromium-browser http://localhost:8080/habpanel/index.html --kiosk --incognito. Die Display-Anweisung ermöglicht den Start auch per ssh aus der Ferne. Im Kiosk-Modus wird direkt im Vollbild gestartet ohne Statusleiste und Kontrollbuttons. Der Incognito-Mode letztendlich verhindert bei Abstürzen Fehlermeldungen beim Neustart und soll auch die Anzahl der Zugriffe auf die Festplatte (bzw. hier auch gerne einmal der Flashspeicher, der nicht unendlich viele Schreibzugriffe aushält) deutlich reduzieren3.

Üblicherweise stört der Mauszeiger noch während der Bedienung, aber mit dem Tool unclutter kann dieser ausgeblendet werden. Sowohl Browser als auch unclutter können im Autostart bei jedem Neustart des Raspberry aktiviert werden: nano ~/.config/lxsession/LXDE-pi/autostart

@unclutter -idle 0
@chromium-browser http://localhost:8080/habpanel/index.html --kiosk --incognito

In der Standardeinstellung geht das Panel nach einigen Minuten in den Standby, wacht aber bei jedem Fingerdruck wieder darauf aus. Zu beachten ist lediglich, dass die Touch-Events auch im Schlafmodus an die Oberfläche weiter geleitet werden und damit unter Umständen Aktionen auslösen können. Zumindest auf der normalen Startseite sollten daher die aktiven (klickbaren) Flächen nicht zu groß sein und die Benutzer wissen, wo sie zur Aktivierung des Displays drücken dürfen.

Werden Änderungen an der HABPanel-Oberfläche vorgenommen lässt sich dies bequemer von einem PC aus erledigen. Im Browser im Wandpanel ist dann ein Refresh erforderlich, was im Kiosk-Modus gar nicht so einfach ist. Über DISPLAY=:0 xdotool key ctrl+F5 kann auch per ssh ein Refresh ausgelöst werden und dann ist auch das Wandpanel auf der neuesten Version der Nutzeroberfläche. Der Vorteil ist, dass neue Funktionen erst einmal begrenzt ausprobiert werden können bevor gleich der gesamte Haushalt mit den Neuerung konfrontiert wird.

Andere Möglichkeiten das Setup noch zu erweitern wären der Start von Chromium über supervise, welches den Prozess permanent überwacht und bei Abstürzen neu startet oder ein Browser-Addon, welches nach einer gewissen Idle-Zeit wieder auf die Startseite zurückwechselt.


Der Feind im eigenen Haus - Die Rückkehr

Instabiles Internet: Analysen, Spekulationen und unbefriedigende Ergebnisse

Jetzt ist es hier einige Zeit recht ruhig gewesen, woran lag das? Nun, zu Einem gab es viel zu tun (wann gibt es das nicht?) und zu Anderem war da immer noch diese lästige Problem der instabilen Internetverbindung, was doch immer irgendwie im Hinterkopf schwebt (genaus wie die Klagen zuhause, dass Internet wäre ständig weg). Die Analysen und Spekulationen haben entsprechend Zeit gekostet bis immerhin ein (nicht ganz befriedigender) Workaround zu finden war.

Was bisher geschah

In Der Feind in meinem Haus ging es das erste Mal um Probleme mit dem Internet. Damals brach unregelmäßig die Verbindung vom Router zum WAN ab, Ursache unklar. Erste Vermutungen brachten einen mDNS-Storm verursacht durch Google-castfähige Geräte ins Spiel, was ungefähr auch zu diesem Zeitpunkt durch die Presse ging. Das mDNS-Problem war real, die (alleinige) Ursache war es aber nicht. Also weitersuchen und letztendlich bliebe nur eine Störungsmeldung beim Provider.

Ursachensuche

Um dem Provider nicht unpräzise mitteilen zu müssen: Internet fällt aus. Wann, wie oft? Weiß nicht, gefühlt immer dann wenn man es braucht. war der erste Schritt überhaupt einmal mitzuloggen, wann es überhaupt zu Problemen kommt. Vielleicht zeigen sich da schon Regelmäßigkeiten. Als schnell ein Skript erstellt, das eine externe Webseite aufruft, auf dem Raspberry, der sowieso 24/7 läuft, installiert und erst einmal Daten gesammelt.

#!/bin/bash
while true; do
    dt=$(date +%Y%m%d_%H%M%S)
    wget -q --tries=5 --timeout=30 --spider http://<seite_des_vertrauens.com>
    
    if [[ $? -eq 0 ]]; then
        echo "$dt:Online"
    else
        echo "$dt:Offline"
    fi
    sleep 30
done

Die Auswertungen geben dem Gefühl der “ständigen” Ausfälle wenigstens ein konkretes Gesicht: Wie lange, wie oft?

Ausfallzeit je Tag
Ausfallzeiten je Tag zeigen keine offensichtlichbegründbaren Besonderheiten
library(tidyverse)
library(lubridate)

du <- read_delim("dsluptime.log", delim = ":",
                 col_names = c("Time", "Status"),
                 col_types = cols(
                     Time = col_datetime("%Y%m%d_%H%M%S"),
                     Status = col_factor(levels = c("Offline", "Online"))
                 ))

du %>%
    filter(Status ==  "Offline") %>%
    group_by(day=wday(Time, label = TRUE, week_start = 1)) %>%
    count(day) %>%
    ggplot(mapping = aes(x = day, y = n * 0.5)) +
    geom_col() + labs(x = "Wochentag", y = "Summe Ausfallzeit [Minuten]") 

Die Summe der Ausfallzeiten je Tag ist zwar nicht gleichverteilt, aber richtig interpretierbar sieht es auch nicht aus. Auffällig sind die im Vergleich geringen Ausfälle an Freitagen, das Wochenende ist (überraschenderweise) ebenfalls leicht weniger betroffen, weicht aber nicht allzu stark von den übrigen Wochentagen ab.

Ausfallzeit je Stunde
Die Ausfallzeiten je Stunde zeigen Häufungen im Abendbereich
du %>%
    filter(Status ==  "Offline") %>%
    group_by(day=hour(Time)) %>%
    count(day) %>%
    ggplot(mapping = aes(x = day, y = n * 0.5)) +
    geom_col() + labs(x = "Stunde", y = "Summe Ausfallzeit [Minuten]") 

Bei der Betrachtung der Ausfallzeiten gestaffelt nach Stunden zeigen sich die meisten Ausfälle in den Abendstunden, was eine gewisse Abhängigkeit von der generellen Auslastung erahnen ließe.

Ausfalldauern je Tag
Die Ausfalldauern je Tag zeigen nur bedingt Korrelationen mit dem Gesamtausfall
du %>%
    filter(Status ==  "Offline") %>%
    group_by(date = as_date(Time)) %>%
    count(date) %>%
    mutate(wday = wday(date, label = TRUE, week_start = 1)) %>%
    ggplot(mapping = aes(x = wday, y = n * 0.5)) +
    geom_boxplot()  + labs(x = "Wochentag", y = "Dauer je Ausfall [Minuten]")
Ausfalldauern je Stunde
Die Ausfalldauern je Stunde zeigen nur bedingt Korrelationen mit dem Gesamtausfall
du %>%
    filter(Status ==  "Offline") %>%
    group_by(hour=hour(Time)) %>%
    group_by(date = as_date(Time)) %>%
    count(hour) %>%
    ggplot(mapping = aes(x = factor(hour), y = n * 0.5)) +
    geom_boxplot() + labs(x = "Stunde", y = "Dauer je Ausfall [Minuten]")

Die Boxplots der Ausfälle aufgeteilt jeweils nach Tagen und Stunden (Summe innerhalb jedes einzelnen Tags, jeder einzelnen Stunde an jedem Tag; d.h. zwei Ausfälle innerhalb derselben Stunde werden zusammengefasst) sollen einen möglichen Zusammenhang zwischen den Tagen/Stunden mit insgesamt einer hohen Ausfallzeit und der Dauer innerhalb dieser Stunde zeigen. Bestätigt hat sich das nicht. Hohe Werte für die Gesamtsumme (z.B. in den Abendstunden) ergeben sich nur durch mehr Ausfälle an mehreren Tagen. Insgesamt zeigt sich aber eine hohe Varianz wie schlimm/lang ein Ausfall sein kann.

Lösungsversuche

Damit scheint dann aber fast klar: Der Provider ist schuld und der Knoten überbucht. In den Abendstunden häufen sich die Ausfälle, wenn alle anderen auch Datenverkehr erzeugen. Also wird nichts anderes übrig bleiben als ein Supporticket zu öffnen und eine Lösung zu suchen, hmpf. So der Plan. Bis wir in Urlaub gefahren sind. Das Ticket ist aus Trägheit noch nicht eröffnet, schließlich funktioniert der Internetzugang irgendwie ja doch immer mal wieder, und Lust auf Gespräche mit der Hotline besteht auch nicht.

Und im Urlaub? Nur ein einziger kurzer Miniausfall, was auch alle möglichen Ursachen haben kann. Sollte das Problem doch bei uns im Haus liegen? Während des Urlaubs war WLAN komplett deaktiviert, Internetverkehr nur durch das Skript zum Verbindungsstatus und Kleinigkeiten wie hin und wieder mal ein NTP-Synchronisation.

WLAN jetzt woanders

Die Easybox 804 wird in Foren gerne für ihr WLAN kritisiert. Falls dort das Problem läge, ließe sich das mit anderer Hardware lösen. Gefallen ist die Wahl auf einen Ubiquit Unifi Accesspoint AC AP Lite, der in Bewertungen als recht konfigurierbar und stabil gilt, aber für kleine Setups eine recht umständliche zu nutzende Konfigurationssoftware besitzt. Die Reichweite der Easybox war zwar in Ordnung, aber ein zweiter Accesspoint sollte eh die Abdeckung verbessern.

Angeschafft, konfiguriert, WLAN an der Easybox abgeschaltet, und geholfen hat es nichts.

Bis zu dem Zeitpunkt als während des Ausräumens der Waschmaschine wieder der Ruf kam: “Kein Internet!”. Ein zufälliger Blick auf den Switch zeigte zwei wild blinkende LEDs, merkwürdig. Gehören tun sie zum neuen Access Point und dem Router. Da war wohl gerade ziemlich Last auf der Verbindung und über den Router sollte eben nur Datenverkehr ins Internet gehen. Im Wohnzimmer zeigt die beste Gattin der Welt zum Beweis des fehlenden Internets auf ihr Huawei P10. Auch andere Geräte im Haus haben kein Netz und der Router selbst loggt wieder seine WAN-Disconnects. Die Bitte, das P10 auszuschalten zeigt zunächst keine Wirkung (wie auch, Android-Smartphones sind ja nicht wirklich aus wenn der Bildschirm aus ist, sondern treiben wer weiß was im Hintergrund) und erst das echte Herunterfahren bringt die LEDs zum Schweigen, bzw. Dauerleuchten. Internet ist prompt auch wieder da…

Logging voraus

Da wäre als ein Kandidat als mögliche Ursache für die Verbindungsabbrüche vorhanden, aber was macht das Smartphone da überhaupt? Wird der Router überlastet wie potenziell bei den mDNS-Broadcasts (wobei der ein wenig Datenverkehr schon aushalten sollte).

Jetzt hat sich die Anschaffung eines Smart-Switches doch noch gelohnt. Den Port zum Festnetz-PC als Monitoringport konfiguriert und Wireshark mitlaufen lassen um zu sehen, wie viel Datenverkehr wohin überhaupt vorkommt.

# Aufzeichnung nur für einen Client (hier das Smartphone)
# und Beschränkung auf 50 MByte-Dateien
dumpcap -i eth0 - -b filesize:50000 -f "host <clientip>" -w trace.pcap

Und in der Tat zeigt das Log gleich mehrere Datenverbindungen zu unterschiedlichen IP-Adressen und volle Auslastung im Upload und auch nicht wenig im Download, allerdings nicht an der Maximalgeschwindigkeit. Immerhin besitzen wir hier eine ultraschnelle Breitbandanbindung mit bis zu 16 MBit/s im Down- und 1 MBit/s im Upstream. Und ich habe mal gedacht, wir würden im Ruhrgebiet, einer der dichtbesiedelsten Region Deutschlands, und nicht auf dem Land leben.

Im P10 selbst lässt sich zusätzlich auch für WLAN mit immerhin stündlicher Auflösung der Datenverbrauch von Apps anzeigen. Sortiert nach Datenvolumen zeigen sich die üblichen Verdächtigen: Google Fotos, Google-Backup-Transferdienst, K9-Mail, WhatsApp, PlayStore, HiCare, Wetterdatenservice, Instagram. Im Wireshark-Trace lässt sich dies über Reverse-Lookup der IP-Adressen zumindest teilweise nachvollziehen, jeglicher auffälliger Traffic würde darin aber vermutlich untergehen.

Energiesparen

Aber warum stürmen die Apps nahezu gleichzeitig auf das WLAN los? Eines der Feature (manche bezeichnen es aber auch als Bug) ist das aggressive Energiesparen im angepassten Android von Huawei. Kein Wunder, wenn die Prozesse beschränkt werden, schlafen liegen und dann gesammelt bei jeder sich bietenden Gelegenheit in den Wettbewerb der schnellsten Aktualisierung treten. Nur lässt sich daran etwas ändern und verschwinden die Verbindungsabbrüche, wenn sich die Datenverbindungen zeitlich entzerren lassen?

In der Huawei FAQ gibt es sogar einen eigenen Unterpunkt, der sich mit dem Wechsel auf WLAN beschäftigt, was auch der Fall wäre wenn das Gerät aus dem Standby erwacht. Zitat:

Nach der Verbindung mit einem WLAN-Netzwerk oder beim Umschalten zwischen einer WLAN- und einer mobilen Datenverbindung, warten Sie etwa 30 Sekunden, bevor Sie Apps verwenden, und überprüfen Sie dann, ob die Internet-Geschwindigkeit normal ist.

Strom sparen (versuchen) bis zum letzten Tropfen, aber die User Experience so weit vernachlässigen, dass es eines eigenen FAQ-Eintrags bedarf, ts. Das lässt sich technisch auch anders lösen.

Ein Versuch dies zu entzerren war die Energiespareinstellungen für die intensivsten Apps, insbesondere Fotos, zu deaktivieren und analog den unbeschränkten Datenzugriff, auch im Hintergrund, zu aktivieren. Wo diese Einstellungen liegen lohnt gar nicht zu dokumentieren, das ändert sich anscheinend öfter, denn fast alle Beiträge mit Beschreibungen, wo diese zu finden wären, waren schon veraltet. Für nicht ganz so wichtige Apps wurde der Datenzugriff gleich ganz verboten…

Aber es lässt sich schon erahnen: Gebracht hat es nichts. Über Maßnahmen wie Löschen des Caches oder Einstellungen in den Apps müssen wir auch nicht weiter reden. Irgendwann einmal hatte Google Fotos auch die Einstellung nur beim Laden zu sichern. Gone with the wind…

Bufferbloat

Eigentlich ist das Internet auf Fairness aufgebaut. TCP-Verbindungen sollten sich schön die verfügbare Bandbreite aufteilen, so dass für jeden etwas bleibt. Klar, insbesondere im schmalen Uplink bleibt dann nicht mehr viel übrig, aber ganz ausgehungert sollte keine Verbindung sein. Bis die Recherchen rund um die Verbindungsabbrüche auf die Seite über Bufferbloat geführt haben. Bufferbloat? Aber Speedtests waren doch alle in Ordnung? Nach Schwierigkeiten als der Anschluss noch neu war haben sich die Speedtests diverser Webseiten fast auf dem Maximum des Anschlusses eingependelt.

Aber Bufferbloat ist anders. Ist der Puffer bei einem der Geräte auf der Wegstrecke, und bei DSL ist dies üblicherweise der Router, zu groß, funktionieren die Ausgleichsmechanismen nicht mehr. Einzelne Pakete oder langsamere Datenverbindungen stecken dann im Puffer fest, die Latenzen schießen in die Höhe. Mittels einem kontinuierlichen Ping während eines Speedtests oder Tests wie fast.com von Netflix lässt sich das Problem relativ leicht nachvollziehen: Steigt die Latenz während einer ausgelasteten Verbindung an gibt es vermutlich ein Bufferbloat-Problem und letztendlich leidet auch hier die Nutzererfahrung. Und siehe da: Insbesondere im Uplink steigt die Latenz um den Faktor 10. Ist die Easybox die Ursache? Danach sieht es aus, müsste aber im Tausch mit anderen Geräten verifiziert werden. Vielleicht bekommt man eben doch nur das, was man bezahlt.

Aber, wir erinnern uns: Es sind nicht nur einzelne Verbindungen und Endgeräte, die den Kürzeren ziehen, auch die gesamte WAN-Verbindung fällt regelmäßig aus.

Workaround: Drosseln

So nicht zu erklären sind eben diese Abbrüche. Um eine Überlastung des Routers auszuschließen bleibt nur noch die Möglichkeit den Datenverkehr unter die Maximalgeschwindigkeit zu drosseln. Der Unifi Access Point bietet die Möglichkeit dies für gesamte SSIDs und sogar einzelne Clients zu tun. Hurra! Dann wird das P10 eben gebremst. Funktioniert aber nicht, hmpf. Scheint ein Problem in der aktuellen Firmware zu sein. Laut Ubiquiti-Foren kommt es öfter vor, dass Funktionen in der einen Firmware funktionieren und der nächsten wieder nicht. Auf Abhilfe zu warten ist nicht zielführend, also wieder auf den Smart Switch zurückgegriffen und den ganzen Port gedrosselt. Sicherheitshalber auf 10 MBit/s im Down- und 800 KBit/s im Uplink, was noch genügend Reserven zur Maximalrate lässt, aber gerade für Smartphones beim Browsen nicht allzu sehr auffällt. Dumm nur, dass damit auch alle anderen WLAN-Geräte betroffen sind, die vielleicht gerne doch in voller Geschwindigkeit im Heimnetz unterwegs gewesen wären.

Aber siehe da, seit der Drosselung kein einziger WAN-Verbindungsabbruch mehr. Natürlich besteht das Problem der im Endgerät lokal verstopften Leitung oder von Bufferbloat generell immer noch, allerdings in deutlich abgeschwächter Form und der Eindruck Internet weg entsteht gar nicht mehr. Damit lässt sich, zumindest temporär, leben.

Das Ende

Die eigentliche Ursache ist aber immer noch unklar und doch ein Fall für eine Störungsmeldung an den Provider. Überlastung, nur weil der Link voll ausgenutzt wird, dürfte auch im Consumergerät kein Problem sein. Mit üblichen Speedtests, auch mehrerer Datenströme parallel, ließen sich die Abbrüche nicht nachstellen.

Aber nicht zu jedem Ausfall der externen Internetanbindung sah der Datenverkehr nach einer wilden Ansammlung angestauter Datenverbindung aus. Ein einzelner Datenstrom konnte ebenso zum Ausfall führen.

Wireshark Trace
Wireshark-Trace einer einzelnen QUIC-Verbindung mit maximaler Uploadrate

QUIC (alias GQUIC), was ist QUIC? UDP-Datenverkehr und das auch noch auf Port 443 (https), welchen Sinn hat das denn?

QUIC ist ein auf UDP basierendes Protokoll, von Google ins Leben gerufen, als Testfeld für neue Managementalgorithmen für die Datenverbindung, unter anderem weil sich mit den normalen Standardisierungsprozessen z.B. auf TCP zu wenig/langsam Einfluss nehmen ließe. QUIC selbst ist schon einige Jahre alt (ich werde auch alt, das Protokoll kannte ich noch nicht) und findet zunehmend Einsatz in Google-Produkten. Und in der Tat zeigt ein Reverse-Lookup für die IP-Adresse auf Google-Server. Das muss allerdings noch nicht auf ein Google-Produkt als Quelle hinweisen, da unter Umständen auch die Nutzung von Google-APIs durch Drittentwickler QUIC nutzen könnte, allerdings waren diverse Google-Dienste und insbesondere die Foto-App in der Summe des Datenvolumens immer vorne mit dabei.

Ein Durchsatztest für QUIC scheint noch nicht zu existieren, das Protokoll ist auch noch im Standardisierungsprozess, also fix selbst einen Loadtest auf Basis einer freien Implementierung entworfen: QUIC-Loadtest.

# Server
quic-loadtest -s <publicip>:4242 -q -p 1300
# Client
quic-loadtest -c <publicip>:4242 -d 600 -q -p 1300

Aber auch dieser Test zeigt wunderbaren Durchsatz im Bereich der zugesicherten Geschwindigkeit. Als einziger offensichtlicher Unterschied zum Trace bleibt nur noch der Port 443. Also das Experiment noch einmal wiederholt, in diesem Durchlauf dann mit privilegiertem Port für den Server (entsprechend als Root, schön öffentlich erreichbar im gesamten Internet, also bloß schnell mit dem Test sein).

Netzwerkverlauf
Netzwerkverlauf/Durchsatz bis zum Verbindungsabbruch

Und jetzt - endlich - bricht die WAN-Verbindung ab. Wiederholungen zeigen immer das gleiche Bild: Perfekter Durchsatz auf Port 4242 (ebenso auf 30, ebenfalls im Bereich der privilegierten Ports, aber keinem Protokoll zugeordnet), auf Port 443 dagegen nach 2 Minuten, spätestens nach knapp 10 Minuten Zwangstrennung. Ob durch den Router oder von außerhalb durch den Provider ist aus dem Log nicht zu entnehmen. Aber spätestens ab hier sieht es nach Absicht aus.

Warum nur wird die Verbindung nur bei UDP-Verkehr auf dem HTTPS-Port und nur unter Volllast beendet? Soll das eine Denial-of-Service-Abwehr sein, da UPD-Verkehr auf diesem Port eher ungewöhnlich ist (aber mit Google als Hauptnutzer dann doch wieder nicht so ungewöhnlich). Liegt einfach ein Fehlkonfiguration vor? Sind die Experimente nicht korrekt und das Problem liegt doch irgendwo anders? Um sicher zu gehen wären weitere und unabhängige Tests notwendig. Und warum sollte das keinem vorher aufgefallen sein? Ein dumme Kombination aus Anschluss, Router und Endgeräten?

Der First-Level-Support des Providers zeigte sich zwar noch interessiert und wollte das Vorgehen zur Reproduktion des Ausfalls an die Technik weitergeben, ein Techniker würde sich direkt zeitnah melden, passiert ist aber nichts.

Eine endgültige Auflösung wird es wohl nicht geben: Der Anschluss beim Provider ist sicherheitshalber gekündigt und das P10 hat Kontakt mit der Badewanne gehabt…


Remote KDE Desktop mit KRFB

Verbindung und Ports absichern

Hin und wieder ist es nötig auf den Desktop eines entfernten, offen im Internet zu erreichenden, Rechner zuzugreifen. Ein Standardmethode ist dabei VNC, zu dem es eine Vielzahl an Implementierungen gibt. Soll der normale, bereits laufende, KDE-Desktop frei gegeben werden kann dies über die Anwendung krfb anstelle einer eigenen, neuen VNC-Sitzung erfolgen.

Um den Zugriff, insbesondere den unbeaufsichtigten Zugriff, abzusichern gibt es dazu ein paar einfache Möglichkeiten:

  1. Sicheres Passwort wählen. Zufällig und lang.
  2. Standardport von 5900 auf einen anderen Port ändern. Diese Maßnahme alleine bringt nur wenig Sicherheit, sondern hilft nur den Scan auf offene VNC-Ports durch Standardskripte zu verhindern.
  3. Externen Zugriff auf den VNC-Port verhindern.

Letzteres erzwingt dann den Zugriff auf die Desktopfreigabe durch eine sichere Umleitung wie einen ssh-Tunnel.

Der Zugriff auf den Port kann per iptables gesperrt werden:

iptables -A INPUT -p tcp -i <eth0> --dport <5912> -j DROP

Werte in spitzen Klammern müssen an das eigene System angepasst werden, d.h. der Name des Netzwerkinterfaces und der Port unter dem krfb auf Anfragen horcht. Diese Regel ist nur bis zu einem Neustart des Systems gültig.

Ein ssh-Tunnel wird dann auf dem Rechner, von dem aus auf die Freigabe zugegriffen werden soll, auf der Kommandozeile aufgebaut:

ssh -N -L 5900:<remotehost>:5912 <remotehost>

<remotehost> ist die IP-Adresse des entfernten Rechners. Der Zugriff auf den Desktop, z.B. mit krdc als Client erfolgt dann über die Angabe von localhost:5900.

Bei Nutzung mit den Standardeinstellungen von krfb für das Framebuffermodul zur Bilderstellung (xcb) und einem Multimonitorbetrieb war dabei das Bild des entfernten Rechners eingefroren. Abhilfe gebracht hat eine Umstellung auf qt für das Framebuffermodul.