- Aufgabenstellung
Dieses Projekt befindet sich noch in der Entwicklungsphase. Die Angaben hier stellen nur den aktuellen Zwischenstand dar und können sich deshalb fortlaufend ändern.
Aufgabenstellung
Zu einer Amateuerfunkanlage soll eine sichere Verbindung zur Steuerung des Antennenrotors aufgebaut werden. Die serielle RS232-Schnittstelle des Antennenrotors soll dabei remote über TCP angesprochen werden.
Softwarekomponenten
Mittels der Software Tailscale kann eine Verbindung zwischen zwei Geräten vermittelt werden. Damit kein externer Cloud-Server von Tailscale verwendet werden muss, läuft auf einem Raspberry Pi, der mit einer festen öffentlichen IP-Adresse ausgestattet ist, die Software headscale in Form eines Docker-Containers. Diese Software dient also als „Vermittlungsstelle“ für die Tailscale-Software auf den beteiligten Clients. Nachdem diese von Headscale die IP-Adressen der Gegenstelle erhalten haben, kommunizieren die Geräte direkt miteinander. Der Headscale-Server ist an dieser Kommunikation nicht mehr beteiligt.
Die serielle Schnittstelle wird durch die Software ser2net mit dem TCP-Netwerk verbunden und mit socat werden die Daten zwischen zwei IP-Adressen transportiert.
Übersicht der Topologie
[Internet]
│
▼ (91.137.72.107)
[Haupt-Raspberry Pi]
│
├── [Traefik (Netzwerk: proxy)] ────┬──── [Headscale-Container] (Port 8080, Domain: headscale.lang-dieter.de)
│ │
│ ▼ (Tailscale-VPN)
│ [Externer Raspberry Pi]
│ │
│ ▼
│ [socat/ser2net] ──── [Funkstation (RS232)]
│
└── [Andere Container]
[Anwender mit Client-Rechner Windows]
│
├── [SPID-Softwar Rotorsteuerung]
│ │
│ ▼ (Tailscale-VPN)
Erweiterung am bestehenden Traefik-Server mit öffentlicher IP
headscale als Container einrichten
Headscale wird auf dem bestehenden Rasperry Pi mit Traefik im Hintergrund aus zusätzlicher Container hinzugefügt.
Ich verwende folgende Verzeichnisstruktur:
/srv/containers/
├── traefik/ # Bestehende Traefik-Instanz (Netzwerk: proxy)
│ └── docker-compose.yml
│
├── headscale/ # Headscale-Instanz
│ ├── docker-compose.yml
│ ├── config/
│ │ ├── config.yaml
│ │ └── acls.yaml
│ └── data/
│
└── … # Andere Container
Inhalt der Datei /src/containers/headscale/docker-compose.yml:
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
read_only: true
tmpfs:
- /var/run/headscale
ports:
- target: 3478
published: 3478
protocol: udp
mode: host
volumes:
- ./config:/etc/headscale:ro
- ./data:/var/lib/headscale
command: serve
healthcheck:
test: ["CMD", "headscale", "health"]
networks:
- proxy
labels:
- traefik.enable=true
# HTTP-Router
- traefik.http.routers.headscale-http.rule=Host(`headscale.lang-dieter.de`)
- traefik.http.routers.headscale-http.entrypoints=web
# HTTPS-Router
- traefik.http.routers.headscale-https.rule=Host(`headscale.lang-dieter.de`)
- traefik.http.routers.headscale-https.entrypoints=websecure
- traefik.http.routers.headscale-https.tls=true
- traefik.http.routers.headscale-https.tls.certresolver=tls_resolver
- traefik.http.routers.headscale-https.tls.domains[0].main=headscale.lang-dieter.de
# Middleware für DERP Websockets
- traefik.http.routers.headscale-https.middlewares=headscale-headers
- traefik.http.middlewares.headscale-headers.headers.customrequestheaders.Connection=Upgrade
- traefik.http.middlewares.headscale-headers.headers.customrequestheaders.Upgrade=websocket
# Service-Konfiguration
- traefik.http.services.headscale.loadbalancer.server.scheme=http
- traefik.http.services.headscale.loadbalancer.server.port=8087
networks:
proxy:
external: true
Die Konfiguration wird in der Datei /srv/containers/headscale/config/config.yaml hinterlegt:
# =============================================================================
# HEADSCALE KONFIGURATION
# Angepasst für Traefik Reverse Proxy & Subdomain headscale.lang-dieter.de
# =============================================================================
# Die externe URL unter der Traefik Ihr Headscale erreichbar macht
server_url: https://lang-dieter.de
# Adressen, auf denen Headscale INNERHALB des Docker-Containers lauscht
listen_addr: 0.0.0.0:8087
metrics_listen_addr: 0.0.0.0:9097
# =============================================================================
# DATENBANK (SQLite)
# =============================================================================
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
# =============================================================================
# NETZWERK-PRÄFIXE (Interne VPN-IPs für Ihre Geräte)
# =============================================================================
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
# =============================================================================
# DNS EINSTELLUNGEN
# =============================================================================
dns:
magic_dns: false
base_domain: net.lang-dieter.de
override_local_dns: true
nameservers:
global:
- 1.1.1.1
- 8.8.8.8
search_domains: []
extra_records: []
# =============================================================================
# KRYPTOGRAFIE SCHLÜSSEL
# =============================================================================
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
# =============================================================================
# DERP (RELAY SERVER) EINSTELLUNGEN
# =============================================================================
derp:
# Keine externen, öffentlichen Tailscale-Server abfragen
urls: []
paths: []
auto_update_enabled: false
# Integrierten DERP-Server aktivieren
server:
enabled: true
region_id: 901
region_code: "raspi-relay"
region_name: "Mein privater Raspi DERP Server"
# WICHTIG: Auf 8088 geändert, um Konflikt mit dem Hauptdienst (8087) zu vermeiden!
listen_addr: "0.0.0.0:8088"
# STUN läuft über UDP und wird in der Compose direkt nach außen gereicht
stun_listen_addr: "0.0.0.0:3478"
private_key_path: /var/lib/headscale/derp_server_private.key
Nun starten wir den Container mit:
docker compose up -d &
Firewall-Regeln am Host ergänzen
sudo ufw allow 3478/udp # STUN für DERP
sudo ufw enable
Headscale-ACLs (Zugangskontrolle einrichten)
sudo nano /srv/containers/headscale/config/acls.yaml
acls:
- action: accept
src: ["100.64.0.0/10"] # Erlaube alle Headscale-IPs
dst: ["100.64.0.0/10:5000"] # Erlaube Zugriff auf Port 5000
Headscale-Benutzer für den Bereich Schlüsselverwaltung anlegen
docker exec -it headscale headscale user create funkstation-admin
Liste der Headscale-Benutzer
anzeigen lassen, da im nächsten Befehl die User-ID benötigt wird (in unserem Fall die Nummer 1)
docker exec -it headscale headscale user list
Auth-Key für den externen Raspberry Pi (an der Funkstation) erzeugen:
docker exec -it headscale headscale preauthkey create --user 1 --expiration=24h --reusable
Den damit erzeugten Key kopieren wir uns, damit er auf dem externen Raspi eingefügt werden kann.
Arbeiten auf externen Rasperry Pi an der Funkstation
Verbindung mit headscale einrichten
Tailscale installieren und den Geräte-Key hinterlegen (ersetze <DEIN_AUTH_KEY> durch den generierten Key):
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server=https://headscale.lang-dieter.de --auth-key=<DEIN_AUTH_KEY> --hostname=funkstation-pi
Wenn alles funktioniert, sollte folgender Befehl eine IP-Adresse im Bereich 100.64.x.y anzeigen:
tailscale status
TCP-to-Serial-Bridge am Funkgeräte-Pi auf Port 5000 zur seriellen Schnittstelle einrichten:
socat TCP-LISTEN:5000,fork /dev/ttyUSB0,raw,echo=0,baud=9600
Für Dauerbetrieb richten wir es als systemd-Dienst ein:
sudo nano /etc/systemd/system/tcp-serial-bridge.service
mit folgendem Inhalt:
[Unit]
Description=TCP to Serial Bridge for Funkstation
After=network.target
[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:5000,fork /dev/ttyUSB0,raw,echo=0,baud=9600
Restart=always
User=root
[Install]
WantedBy=multi-user.target
Liste der Dienste aktualisieren und den neuen Dienst starten:
sudo systemctl daemon-reload
sudo systemctl enable tcp-serial-bridge
sudo systemctl start tcp-serial-bridge
Firewall-Regeln am Funkgeräte-Rechner ergänzen
sudo ufw allow 5000/tcp # TCP-Port für Funkstation
sudo ufw enable