SocketCAN + Docker = Die Lösung

SocketCAN + Docker = Die Lösung

Um alle Punkte in diesem Blog zu verstehen, sind Grundkenntnisse zu Docker und SocketCAN notwendig. Auf Grundlagenwissen wird im Weiteren nicht eingegangen. Der Artikel bietet einen Lösungsansatz, SocketCAN und Docker stabil und komfortabel zu kombinieren.
Achtung Eigenwerbung: Für Wissbegierige und Tech-Freaks bieten wir für den Zeitraum Juli bis September Schulungs- und Entwicklungspakete zu besonderen Konditionen an. Mehr Informationen gibt es hier.

Bei der Entwicklung hochkomplexer embedded Devices, stehen wir oft vor der Herausforderung, mehrere CAN Applikationen unter Nutzung verschiedener Laufzeitumgebungen in einem System gleichzeitig zu testen. Zu diesem sehr speziellen Thema sind bisher nur unzureichende Dokumentationen oder Informationen zu finden. Dies haben wir bei SYS TEC electronic als Gelegenheit genommen, uns selber mit dem Thema auseinanderzusetzen.

In diesem Beitrag wird es deshalb darum gehen, für diese Herausforderung eine Lösung mit SocketCAN und Docker zu finden.
Warum sollte man SocketCAN und Docker überhaupt zusammen nutzen wollen? SocketCAN stellt eine Treibersammlung dar, welche das Verwenden von CAN-Schnittstellen unter Linux erlaubt. Sie bietet dabei aber auch viele weitere Dienste an.

Docker hingegen erlaubt es, Aspekte eines Systems voneinander abzukapseln, beispielsweise um unerwünschte Quereffekte zwischen Programmen oder Applikationen zu vermeiden. Es bietet auch einen gewissen Schutz für das Gesamtsystem, da innerhalb der Docker-Container keine unerwünschten Zugriffe nach „draußen“ möglich sind. Außerdem ist es hervorragend dafür geeignet, auf einer Vielzahl von Maschinen/Geräten, die gleiche Anwendungsumgebung zu schaffen. Das vereinfacht Auslieferungen neuer und aktualisierter Software.

Unser Ziel ist es, auf einem Gerät mehrere Docker-Container zu installieren, welche alle über das CAN-Interface des Hostsystems angesprochen werden können.

Unsere erste Überlegung ist, das Interface selbst direkt in den Docker hineinzureichen. Typischerweise ist an PCs das CAN-Interface über USB angebunden. Diese Lösung funktioniert leider nicht, da die Docker-Container rein im User-Space laufen. Das bedeutet, der Kernel wird nicht mit virtualisiert, im Gegensatz zu einer vollen Hardware-Virtualisierung (typischerweise eine klassische virtuelle Maschine). Weiterhin sind die SocketCAN-Treiber nicht auf libusb basierend, so dass innerhalb des Docker-Containers keine Möglichkeit besteht, ein USB-CAN-Interface zu verwenden.

Ein zweiter Lösungsansatz ist es, den Host-Networking-Mode oder Priviledged Mode mit dem Docker-Container zu verwenden. Dies würde dafür sorgen, dass der Container auf das CAN-Interface des Hosts zugreifen kann. Allerdings ergeben sich dadurch weiterhin einige unerwünschte Nebeneffekte: hierfür werden erweiterte (heißt Root-) Rechte benötigt, um das Networking aufzusetzen. Dies stellt keine sinnvolle Alternative dar, da dadurch die Container-Isolation verloren geht. Das heißt, dass die Applikationen welche im Container laufen, sich genauso verhalten, als würden sie auf dem Host-System arbeiten. Dadurch gehen die entscheidenden Vorteile von Docker jedoch komplett verloren.

Die dritte und unserer Ansicht nach beste Umsetzungsmöglichkeit ist das Verwenden von virtuellen Schnittstellen für die Docker-Container. Diese werden bereits bei der TCP/IP-Kommunikation zwischen Docker-Containern und dem Host-System verwendet.

Konkret verwenden wir hierfür vxcan. Dies wird ebenfalls im Linux Kernel mitgeliefert. Vxcan erlaubt es, virtuelle CAN-Schnittstellen zu erzeugen und zwischen diesen Tunnel einzurichten. So kann man relativ simpel über Network Namespaces hinweg kommunizieren. Beispielsweise zwischen zwei oder mehreren Docker-Containern. Die folgende Abbildung visualisiert dieses Vorgehen. Die beiden netns-Boxen stellen die verschiedenen Netzwerk-Namespaces dar, wobei es sich zum Beispiel um die besagten Docker-Container handeln könnte. Via vxcan können sie untereinander und nach außen hin mit einer virtuellen Schnittstelle kommunizieren.

 

Dies stellt jedoch nur den ersten Schritt der Lösung dar. Nun haben wir zwar virtuelle Schnittstellen für jeden Docker, jedoch keine physische Verbindung an das CAN-Interface des Geräts. Das letzte Puzzleteil hierfür stellt CAN_GW dar. Damit lassen sich physische und virtuelle CAN-Schnittstellen miteinander verbinden bzw. überbrücken (vgl. mit einer Netzwerkbrücke). Das erlaubt es letztendlich, eine oder mehrere virtuelle CAN-Schnittstellen mit dem physisch am Gerät vorhandenen Interface zu verbinden.

 

Dadurch kann der CAN-Bus über die Schnittstelle am Gerät zu den Docker-Containern „angeschlossen“ werden. Jedem Docker wird mittels vxcan eine eigene virtuelle CAN-Schnittstelle zugeordnet. Mittels CAN_GW kann anschließend die am Gerät physisch vorhandene Schnittstelle mit jeder vorher virtuell eingerichteten verbunden werden. So erreichen die CAN-Nachrichten, welche am Gerät ankommen, jeden einzelnen Docker-Container unabhängig voneinander.

Für den Test unserer Demo-Applikation werden zwei Terminalfenster benötigt. Als CAN-Interface kann eines unserer sysWORXX USB-CANmodule  am PC verwendet werden. Die notwendigen Kommandos, sind die folgenden:

1. Terminal

docker run --rm -it --name cantest ubuntu:20.04

apt-get update && apt-get install -y can-utils

2. Terminal

DOCKERPID=$(docker inspect -f '{{ .State.Pid }}' cantest)

sudo ip link add vxcan0 type vxcan peer name vxcan1 netns $DOCKERPID

sudo modprobe can-gw

sudo cangw -A -s can0 -d vxcan0 -e

sudo cangw -A -s vxcan0 -d can0 –e

sudo ip link set vxcan0 up

sudo ip link set can0 type can bitrate 125000

sudo ip link set can0 up

sudo nsenter -t $DOCKERPID -n ip link set vxcan1 up

1. Terminal

candump vxcan1

2. Terminal

cansend can0 123#1122

 

Welche Vorteile bekommt man durch dieses, auf den ersten Blick aufwändige Vorgehen?

  • Alle Systeme und virtuellen Geräte sind voneinander getrennt. Das verringert die „Verschmutzung“ und der Einfluss des Containers hört an seinen Grenzen auf. Dies bietet gleichzeitig einen gewissen Schutz des Host-Systems.
  • Jeder Docker-Container besitzt seine eigene separate IP-Adresse, genauso wie es bei einer realen Applikation der Fall wäre, wenn sie je auf einem separaten Gerät laufen würden.
  • Die Reproduzierbarkeit ist sehr hoch. Durch das Aufsetzen eines Dockerfiles lässt sich das Image und der Container beliebig oft und in verschiedenste Systeme integrieren.
  • Diese Herangehensweise ist viel leichtgewichtiger, als wenn man ein komplettes System als virtuelle Maschine erstellen würde. Gleichzeitig würde jede VM ein eigenes physisch vorhandenes CAN-Interface benötigen.
  • Es ist ebenso möglich, diese Lösung auf eingebetteten Geräten zu verwenden, wie unserem sysWORXX CTR-700.

Allerdings gibt es auch ein paar Nachteile dieser Lösung:

  • Es erfordert einige manuelle Eingriffe, um das Gesamtsystem aufzusetzen.
  • Durch die Verwendung des CAN_GW und die daraus resultierende Verbindung der einzelnen Netzwerke, kommt es zu einem vergrößertem Nachrichten-Overhead, was bei aktuellen Systemen jedoch kaum ins Gewicht fällt.

Eine ausführlichere Demonstration mit weiterführenden Erklärungen und Informationen wurde von unserem Systemarchitekten Daniel Krüger im Rahmen der Chemnitzer Linux-Tage durchgeführt. Das Video, inklusive Präsentationsunterlagen, sind hier zu finden.