Unaligned Access beim Raspberry Pi

Ich habe eine kleine Microcontrollerschaltung mit einem STM32, welche Daten über UART an einen Raspberry PI überträgt. Die Daten sind in einem kleinen Protokoll verpackt, welches auf TLV (Tag Length Value) beruht und über CRC gesichert ist. Darüber werden sowohl char (1 byte) als auch integer und float übertragen (4 Bytes). Dabei wird ein Integer oder Float aus 4 Byte (INT1-4) gebildet. Es sieht dann so aus:

| Tag | Length | Byte   | CRC
| Tag | Length | Byte   | Byte   | Byte   | Byte | CRC
| Tag | Length | INT1   | INT2   | INT3   | INT4 | CRC 
| Tag | Length | FLOAT1 | FLOAT2 | FLOAT3 | FLOAT4 | CRC

Entwickelt habe ich es allerdings am PC in C++. Dort klappt die Wandlung des Streams zurück in ein INT/Float über einen Cast:

char *data;
...
reinterpret_cast<float>(data)
reinterpret_cast<float>(data + 4)

Am PC stellt der Cast kein Problem dar, auch wenn die Adresse nicht ein vielfaches von 4 ist (alignment). Am Raspberry PI allerdings kann der ARM Prozessor den Zugriff nur auf aligned Adressen machen und nicht auf Addressen die ungerade sind (0 = OK / 1,2,3 NOK). Ein X86 Prozessor schafft dies allerdings ist er dabei ein wenig langsamer, was aber kein Problem in diesem Kontext darstellt. Der ARM bricht mit Segmentation Fault ab, da er einen Busfehler feststellt (übrigens genau wie der STM32).

Um das handhaben zu können und das allgemeine Protoll nicht ändern zu müssen, kann man sich mit folgendem Konstrukt behelfen:

float ptr2float(char *ptr)
{
  union p2f
  {
    unsigned char c[4];
     float f;
  } tmp;

  tmp.c[0] = ptr[0];
  tmp.c[1] = ptr[1];
  tmp.c[2] = ptr[2];
  tmp.c[3] = ptr[3];

  return tmp.f;
}

Damit ist der Code portabel und funktioniert auch auf anderen Architekturen. Einen abgewandelten Code verwende ich auch auf dem Embedded Board mit einem STM32. Dabei kann man gleich auch die Konvertierung von little zu big endian machen oder umgekehrt. Ein strcpy oder memmove wäre auch möglich, allerdings ist  der Code meist länger, da man gewisse Dinge abfangen muss und auch das strcopy intern das Alignment beachten muss, was mehr Laufzeit kostet (hierzu sei der GCC Code von strcpy und strncpy empfohlen).

c’t Schlagseite als Bild und PDF

Ich bin langjähriger Leser ders Magizins C’T von des heise Verlags. Die ganzen Hefte aufzuheben, nachdem sie gelesen sind, würde ein eigenes Zimmer benötigen. Am Ende des Jahres archiviere ich manche Artikel. Dabei ist aber auch immer die „Schlagseite“ dabei, dem zweiwöchentlichen Comic der CT. Ein Übersicht über alle Schlagseiten von 2012 weg gibt es hier.

Die Papierseiten habe ich mir letztens angeschaut, und diese waren teilweise sehr sehr gelb! Deswegen habe ich mir gedacht, ich lade sie mir kostenlos von der heise.de Homepage runter und speichere sie als Bild und PDF. Das ist aber bei 26 Heften pro Jahr ganz schön mühsam. Ich habe deswegen meine Zeit darin investiert, das Ganze zu automatisieren. Diesmal in Python – damit geht der Download schnell und einfach.

Wer das Script haben will, kann es unter https://github.com/luckyhacky/schlagseite herunterladen. Kommentare und Anregungen sind gerne willkommen. Das Script ist as-it-is, d.h. es lädt immer alles herunter. Aktuell benötigt es Python 3.

PS.: Einen ähnlichen Ansatz hatte tamagothi.de allerdings war das Script auf die alte Seite bis 2012 ausgelegt. Diese gibt es nicht mehr. Auch die alte Schlagseite vor 2012 scheint nicht mehr verfügbar zu sein.

Kanalratte – IOT

Beim letzten Unwetter (2016) lief der Keller bei Bekannten voll. Daraufhin haben Sie sich eine Rückstauklappe einbauen lassen. Soweit so gut. Nur was geschieht, wenn die Klappe geschlossen ist, und man auf die Toilette muss? Da die Anzeige bzw. die Bedieneinheit auf Grund der Nachrüstung nicht im Haus ist, war eine andere Lösung gefragt. Raus kam die Kanalratte!

Die Idee ist, von der Steuerung, welche ein potentialfreis Wechselrelais hat, welches offen / geschlossen anzeigt, abzugreifen und einem ESP8266 Mikrocontroller zuzuführen.  ESP8266 sind als verschiedene Module erhältlich – bei mir war es ein NodeMCU. Dieses Modul hat die Möglichkeit sich ins WLAN einzuloggen und einen Status abzusetzen. Dazu wird ein HTTP Request über den Dienst Pushover abgesetzt. Bei diesem sind grundlegen bis 7000 Nachrichten pro Monat kostenfrei. Einzig die APP fürs Handy kostet einmalig knapp 5€ pro Android oder IOS Installation. Familienaccounts sind möglich.

Es wird also ein Request abgesetzt, falls sich die Klappe schließt oder öffnet. Zusätzlich sendet die Kanalratte eine Nachricht beim Startup, womit man die generelle Funktionsweise sehen kann. Weiterhin ruft der ESP8266 die Uhrzeit über NTP ab und schickt um 9 und 21 Uhr eine Ping Nachricht, damit man sieht ob er noch am Leben ist. Es nützt nämlich nichts, wenn nie eine Nachricht kommt oder das Netzteil seinen Dienst versagt hat, was man natürlich nicht bemerkt hat.

Der Hardwareaufbau ist dabei simpel. ESP8266 Modul auf einer Lochrasterplatine aufgesteckt oder direkt aufgelötet. Über zwei Verbindungen werden die zwei Kontakte des Wechselschalters im Wechselrelais abgefragt. Da die zwei Eingänge am ESP8266 intern mit Pullup betrieben werden, ist der gemeinsame Kontakt auf Ground gelegt. Das sind die drei nötigen Leitungen. (Hinweis: in Version 1.1 sind noch zwei 1k Wiederstände in die beiden Leitungen eingeschleift um den Strom zu begrenzen.)

Die Software ist simpel aufgebaut. Es werden die GPIO 12 und 13 als Input verwendet, der WLAN Zugang konfiguriert und die Zeitsynchronisierung über Callback aufgebaut. Die Logik für die GPIO überprüft zyklisch, ob die Klappe auf ist (Zustand der Leitungspaare ein / aus) oder zu ist (Zustand der Leitungspaare aus / ein) oder ob ein Leitungsproblem vorliegt (ein/ein oder aus/aus). Bei einer Statusänderung wird die API Seite von Pushover aufgerufen und so der Status abgesetzt, welcher Sekunden später am Smartphone oder PC erscheint. Als vorgefertigte Bibliotheken müssen nur TimeAlarm, TimeLib und NTPClient in der Arduino IDE nachinstalliert werden.

Quelltext und Konfiguration sind hier erhältlich: Kanalratte.ino config.h

Die Daten für die Pushover API müssen aus dem internen Bereich von Pushover nur noch in die config.h eingesetzt werden und es kann losgehen.

Steuerung Rückstauklappe

Kanalratte Prototyp

Backup mit PHP + FTP + SSL Upload

Wie in meinem vorherigen Beitrag geschrieben, gibt es einige Möglichkeiten, ein Backup zu machen. Dabei habe ich lange die Methode mit FTP über SSL eingesetzt. Hier der kurze Writeup, da man die Scripte auch für andere Sachen verwenden kann.

Datenbankbackup mit Upload:

<?
// Backup erstellen
system("mysqldump -uUSERNAME -h127.0.0.1 --port=3306 -pPASSWORD --all-databases > /tmp/dump.sql");

// SSL-Verbindung aufbauen
$conn_id = ftp_ssl_connect("server08.storage.domain.de");
if($conn_id)
 {
 // Login mit Benutzername und Passwort
 $login_result = ftp_login($conn_id, "ftpUSERNAME", "ftpPASSWORD");

// Datei hochladen
 if(ftp_put($conn_id, "backup_".date("Y-m-d").".sql", "/tmp/dump.sql", FTP_BINARY))
 {
 echo "INFO: Datei erfolgreich hochgeladen\n";
 }
 else
 {
 echo "ERROR: FTP-Backup nicht erfolgreich!\n";
 }

// Dump löschen
 unlink("/tmp/dump.sql");

// SSL-Verbindung schließen
 ftp_close($conn_id);
 }
else
 {
 echo("ERROR:Login failed!");
 }

?>

Zum anzeigen der vorhandenen Dateien auf dem FTP Server reicht folgendes Snippet:

<?

// SSL-Verbindung aufbauen
$conn_id = ftp_ssl_connect("server08.storage.domain.de");
if($conn_id)
 {
 // Login mit Benutzername und Passwort
 $login_result = ftp_login($conn_id, "ftpUSERNAME", "ftpPASSWORD");

var_dump(ftp_nlist($conn_id, "."));

// SSL-Verbindung schließen
 ftp_close($conn_id);
 }
else
 {
 echo("ERROR:Login failed!");
 }

?>

Wenn man Jahreweis löschen will kann man es hiermit versuchen:

<?

$year = 2000; // Year to delete

// SSL-Verbindung aufbauen
 $conn_id = ftp_ssl_connect("server08.storage.domain.de");
 if($conn_id)
 {
 // Login mit Benutzername und Passwort
 $login_result = ftp_login($conn_id, "ftpUSERNAME", "ftpPASSWORD");

$list = ftp_nlist($conn_id, ".");

foreach($list as $k => $v)
 {
 if(substr($v, 0, 11) == "backup_".$year)
 {
 echo("delete ".$v."\n");
 ftp_delete($conn_id, $v);
 }
 }

// SSL-Verbindung schließen
 ftp_close($conn_id);
 }
 else
 {
 echo("ERROR:Login failed!");
 }

?>

Einzig was bei der Vergindung zum FTP Server nicht geprüft wird, ist das Zertifikat. Es kann also immer noch wer sich als FTP Server ausgeben.

Webspace Backup

Trotz guter Absicherung von medernen Hostern sollte man seine Daten sichern. Damit kann man auch Stände wieder herstellen, z.B. wenn man versehentlich was gelöscht hat, das aber wieder online stellen will.

Dazu gibt es verschiedene Möglichkeiten:

  • 1) Backup durch Hoster
  • 2) Lokales Backup
  • 3) Backup mit Mailversand
  • 4) Kommerzielles Produkte mit FTP Upload
  • 5) Ablage am Server und abholen
  • 6) On-the-fly Komprimierung und Übertragung mit SCP

Ich habe alle Ausprobiert und habe hier zusammengeschrieben, welche möglichkeiten es gibt und welche ich bereits ausprobiert habe über die letzten 10 Jahre.

zu 1) Backup durch Hoster

Ein Lokales Backup auf dem Server hilft zwar dabei Daten wiederherzustellen, die man gelöscht hat, aber wenn der Server den Dienst verweigert, dann sind die Daten verloren. Manche Hoster haben ein spezielles Verzeichnis, dass meist auf einem anderen Server liegt und so diesen Nachteil ausgleicht. Auch gibt es meist in der Administrationskonsole der Hoster Möglichkeiten, das Backup automatisch auf einen anderen Server zu übertragen. Backup ist hier meist kostenlos, aber da der Betreiber auch die zusätzliche Speicherkapazität sich bezahlen lässt, kommt es zu dem Punkt, wo man bezahlen muss, nämlich der Wiederherstellung (Restore).

zu 2) Lokales Backup

Man kann das Backup auch selber durchführen, wenn man Zugriff über SSH auf die Konsole oder wenn man einen (V)Server gebucht hat.

Beispiel für ein eigenes Backupscript:

#!/bin/sh

DATE=`date +%F`

################################
# database backup

cd /backup/data/
mysqldump -C -c --add-drop-table --allow-keywords -l -Q --user=longusername --password=longpassword --databases database > backup-db-${DATE}.sql

tar czvf backup-db-${DATE}.tar.gz backup-db-${DATE}.sql
rm -f /backup/data/backup-db-$DATE.sql


################################
# web backup
tar czvf backup-www-${DATE}.tar.gz /var/www/*

Wer die letzten Backups der letzten 40 Tage behalten will und alles andere löschen will, kann es hiermit versuchen.

################################
# remove old data (older than 40 days)
rm -f `find /backup/data/ -mtime +40`

Das ganze kann als Cronjob verpackt werden und jeden Tag ausgeführt werden. Zum editieren des Cronjobs „crontab -e“ eingeben. Im folgenden Beispiel wird der Cronjob um 3 Uhr morgen ausgeführt, wenn die meisten Besucher schlafen.

00 3 * * * /backup/scripts/backup.sh

zu 3) Backup mit Mailversand

Wenn man die Backups in eigener Hand halten will, kann man sich auch die Backups zusenden lassen, solange das Empfängerpostfach groß genug ist. Die aktuellen Postfächer bieten meist diese Möglichkeit, wenn die Backupgröße unter 20 oder 50MB bleibt. Allerdings sollte man sich bewußt sein, dass das Backup ggf. ungesichert über die Leitung geht und dort sensible Daten enthalten sein können.

Um eine Mail aus dem Backup zu erzeugen verwendet man folgende Befehle, die man natürlich auch ins Script einbauen kann:

uuenview -b -s 'test' -m user@email.de /backup/data/backup-db-${DATE}.tar.gz

Erzeugt man das Backup als root, so ist es sinnvoll, es mit einem „echten“ Benutzernamen zu versenden.

su -c "uuenview -b -s 'test' -m user@email.de /backup/data/backup-db-${DATE}.tar.gz" username

zu 4) Kommerzielle Produkte mit FTP Upload

eine weitere Möglichkeit stellen kommerzielle FTP Speicher dar. Diese sind meist auf 1-2 User oder IP Adressen begrenzt. Die Kosten halten sich auch in Grenzen und man hat einen eigenen externen Speicher. Da die Übertragung hier meist ungesichert stattfindet, hat diese Möglichkeit auch Nachteile.

Hier hilft auch wieder ein einfaches Script:

#!/bin/bash
#############################
# FTP backup

DATE=`date +%F`
cd /backup/data/

/usr/bin/ftp -i -v server08.storage.hoster.de <<EOF
binary
put backup-db-$DATE.tar.gz
quit
EOF

Die Zugangsdaten stehen in der Datei .netrc im Verzeichnis des ausführenden Benutzers:

machine server08.storage.hoster.de login ftp-username password ftp-password

zu 5) Ablage am Server und abholen

Bis hierhin haben sich die oben genannten Methoden bewärt, auch wenn sie nach dem Stand der Sicherheit nicht mehr die aktuellsten waren / sind.

Deswegen habe ich meinen kleinen Raspberry Pi bzw. Banana PI damit beauftragt, in der Nacht die Daten von Server abzuholen. Dazu wir das Backupscript aus 2) ausgeführt und der Raspberry holt sich die Daten auf den lokalen Speicher über eine gesicherte Verbindung ab.

Dazu habe ich ein SSH Public/Private Keypair erzeugt und es auf den Server abgelegt (Weiterführende Informationen gibt es bei Google).

Nachdem das Backup um 3.00Uhr gelaufen ist, wird um 3.10Uhr folgendes Script am lokalen Linux Host gestartet.

scp -q -P 22 user@domain.de:/backup/data/backup-db-`date +%Y-%m-%d`.tar.gz /mnt/storage/data/backup/domain.de/

Eine weitere Variante ist, dass die Vorbedingungen gecheckt werden und das Ergebnis über den Dienst Pushover gesendet wird.

#!/bin/bash

# check mount
if ! mountpoint -q /mnt/storage/
then
 curl -s --form-string "token=aTOKEN" --form-string "user=uUSERTOKEN" --form-string "message=ERROR: Backup dir not mounted" https://api.pushover.net/1/messages.json > /dev/null
 exit
fi

# check space - 10GB should be there
SPACE=`df -BM /mnt/storage | grep "/mnt/storage$" | awk '{ print $4}' | cut -d'M' -f1`
if [ $SPACE -lt "10000" ]
then
 curl -s --form-string "token=TOKEN" --form-string "user=USERTOKEN" --form-string "message=WARN: Space on backup dir low" https://api.pushover.net/1/messages.json > /dev/null
fi

# download yesterdays file & check
scp -q -P 22 user@domain.de:/backup/data/backup-db-`date +%Y-%m-%d`.tar.gz /mnt/storage/data/backup/domain.de/

if [ "$?" -eq "0" ]
then
 curl -s --form-string "atoken=TOKEN" --form-string "user=uUSERTOKEN" --form-string "message=INFO: Backup successful" https://api.pushover.net/1/messages.json > /dev/null
else
 curl -s --form-string "atoken=TOKEN" --form-string "user=uUSERTOKEN" --form-string "message=ERROR: Copy from Host failed $?" https://api.pushover.net/1/messages.json > /dev/null
fi

zu 6) On-the-fly Komprimierung und Übertragung mit SCP

Sollte der Hoster weitere Möglichkeiten bereitstellen, so kann man sich die Daten auch on-the-fly vom Linux Backup Host holen und vom Server komprimieren lassen.

Beispiel für Mysql

ssh user@domain.de 'cd /tmp/ && mysqldump -uusername -h127.0.0.1 --port=3307 -ppassword --all-databases | gzip -c -9' > backup_mysql_`date +%Y-%m-%d`.gz

Beispiel für Webspace

ssh user@domain.de 'cd /home/www/ && tar -cf - public | gzip -9' > backup_www_`date +%Y-%m-%d`.tar.gz

Auch hier ist es wichtig, dass man vorher überprüft im Script, ob genügen Speicher vorhanden ist. Auch sollte man sicherstellen, dass SSH sauber beendet wird, ohne das die Verbindung fehlschlägt.

ssh user@domain.de 'cd /home/www/ && tar -cf - public | gzip -9' > backup_www_`date +%Y-%m-%d`.tar.gz
if [ "$?" -eq "0" ]
then
 BACK_HOSTING="OK"
else
 BACK_HOSTING="FAILED"
fi

ssh user@domain.de 'cd /tmp/ && mysqldump -uusername -h127.0.0.1 --port=3307 -ppassword --all-databases | gzip -c -9' > backup_mysql_`date +%Y-%m-%d`.gz
if [ "$?" -eq "0" ]
then
 BACK_MYSQL="OK"
else
 BACK_MYSQL="FAILED"
fi

Das Ergebnis kann man sich dann wieder per Email oder Pushdienst mitteilen lassen.

curl -s --form-string "token=aTOKEN" --form-string "user=uUSERTOKEN" --form-string "message=INFO: Backup done
 Backup hosting: ${BACK_HOSTING}
 Backup mysql: ${BACK_MYSQL}" https://api.pushover.net/1/messages.json > /dev/null

Fazit:

Die letze vorgestellte Methode stellt meiner Meinung nach die sicherste Methode in Bezug auf Übertragung und Datenhaltung dar. Diese setze ich bei meinen Projekten ein.

Wer weitere Vorschläge oder Verbesserungen hat, kann sie mir gerne mailen oder einen Kommentar hinterlassen.