15 Netzwerkprogrammierung mit Bash

15.1 Grundlegende Netzwerkbefehle und ihre Integration

Die Bash-Shell bietet zahlreiche Möglichkeiten zur Interaktion mit Netzwerkressourcen. Durch die geschickte Kombination von Netzwerkbefehlen in Shell-Skripten können Administratoren und Entwickler effiziente Werkzeuge zur Überwachung, Diagnose und Automatisierung von Netzwerkaufgaben erstellen.

15.1.1 Netzwerkdiagnose-Befehle

Die folgenden Kernbefehle bilden das Fundament für Netzwerkoperationen unter Linux:

ping: Prüft die Erreichbarkeit eines Hosts durch das Senden von ICMP-Echo-Anfragen.

# Grundlegende Verwendung
ping example.com

# Begrenzung auf eine bestimmte Anzahl von Anfragen
ping -c 4 192.168.1.1

# Intervall zwischen Ping-Anfragen festlegen (in Sekunden)
ping -i 2 example.com

traceroute/tracepath: Zeigt den Pfad an, den Pakete zu einem Zielhost nehmen, und misst die Übertragungsverzögerung.

# Standardverwendung
traceroute example.com

# Verwendung mit UDP-Paketen
traceroute -U example.com

# Alternative mit weniger Privilegien
tracepath example.com

netstat/ss: Zeigt Netzwerkverbindungen, Routing-Tabellen und Schnittstellenstatistiken an. Der neuere ss-Befehl ist schneller und bietet erweiterte Funktionen.

# Alle offenen Ports anzeigen
netstat -tuln

# Verbindungen mit PID und Programmnamen
netstat -tulnp

# Mit ss (Socket Statistics)
ss -tuln

ip: Ein leistungsstarkes Werkzeug zur Konfiguration und Anzeige von Netzwerkschnittstellen, Routen und mehr. Es ersetzt ältere Befehle wie ifconfig und route.

# Netzwerkschnittstellen anzeigen
ip addr show

# Routing-Tabelle anzeigen
ip route show

# ARP-Tabelle anzeigen
ip neigh show

dig/nslookup/host: Werkzeuge zur DNS-Abfrage und -Diagnose.

# Detaillierte DNS-Informationen abfragen
dig example.com

# Einfachere Alternative
nslookup example.com

# Kompakte Ausgabe
host example.com

# Reverse-DNS-Lookup
dig -x 8.8.8.8

15.1.2 Netzwerküberwachung in Shell-Skripten

Ein einfaches Skript zur Überwachung der Erreichbarkeit mehrerer Hosts könnte wie folgt aussehen:

#!/bin/bash

# Datei mit Hostnamen oder IP-Adressen
HOSTS_FILE="/path/to/hosts.txt"
LOG_FILE="/var/log/host_monitor.log"
MAX_TIMEOUT=2  # Timeout in Sekunden

if [[ ! -f "$HOSTS_FILE" ]]; then
    echo "Fehler: Hosts-Datei nicht gefunden: $HOSTS_FILE" >&2
    exit 1
fi

date_str=$(date "+%Y-%m-%d %H:%M:%S")
echo "=== Netzwerk-Check gestartet: $date_str ===" >> "$LOG_FILE"

while read -r host; do
    # Kommentare und leere Zeilen überspringen
    [[ "$host" =~ ^# ]] || [[ -z "$host" ]] && continue
    
    echo "Prüfe Host: $host" >> "$LOG_FILE"
    
    # Ping mit einem Timeout und nur einem Paket
    if ping -c 1 -W "$MAX_TIMEOUT" "$host" &>/dev/null; then
        echo "  Status: ERREICHBAR" >> "$LOG_FILE"
    else
        echo "  Status: NICHT ERREICHBAR" >> "$LOG_FILE"
        # Optional: Alarmierung per E-Mail oder andere Aktionen
    fi
done < "$HOSTS_FILE"

echo "=== Netzwerk-Check beendet ===" >> "$LOG_FILE"

15.1.3 Arbeiten mit Sockets über /dev/tcp und /dev/udp

Die Bash bietet eine spezielle Dateisystem-Notation, mit der direkt auf TCP- und UDP-Sockets zugegriffen werden kann. Diese Funktionalität ist extrem nützlich, wenn keine spezialisierten Netzwerktools verfügbar sind.

#!/bin/bash

# HTTP-Anfrage an einen Webserver senden
exec 3<>/dev/tcp/example.com/80
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" >&3
cat <&3
exec 3>&-  # Socket schließen

Dieses Beispiel öffnet eine TCP-Verbindung zum Port 80 von example.com, sendet eine HTTP-Anfrage und gibt die Antwort aus.

15.1.4 Port-Scanning mit Bash

Ein einfacher Port-Scanner kann mit der /dev/tcp-Notation implementiert werden:

#!/bin/bash

HOST="192.168.1.1"
START_PORT=1
END_PORT=1024
TIMEOUT=1

echo "Scanne Ports $START_PORT bis $END_PORT auf $HOST..."

for port in $(seq $START_PORT $END_PORT); do
    (timeout $TIMEOUT bash -c "echo > /dev/tcp/$HOST/$port" 2>/dev/null && 
        echo "Port $port ist offen") &
    
    # Begrenze die Anzahl der gleichzeitigen Prozesse
    if [[ $(jobs -r | wc -l) -ge 20 ]]; then
        wait -n
    fi
done

wait  # Warte auf alle Hintergrundprozesse
echo "Scan abgeschlossen."

15.1.5 Überwachung von Netzwerkbandbreite

Mit den Befehlen iftop oder nethogs können Sie die Netzwerknutzung überwachen. Ein einfaches Skript zur regelmäßigen Erfassung der Netzwerkstatistiken:

#!/bin/bash

INTERFACE="eth0"
LOG_FILE="/var/log/network_stats.log"
INTERVAL=300  # Sekunden

# Prüfe, ob das Interface existiert
if ! ip link show "$INTERFACE" &>/dev/null; then
    echo "Fehler: Interface $INTERFACE nicht gefunden" >&2
    exit 1
fi

while true; do
    timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    
    # Netzwerkstatistiken aus /proc/net/dev erfassen
    stats=$(grep "$INTERFACE:" /proc/net/dev | awk '{print $2, $10}')
    rx_bytes=$(echo "$stats" | awk '{print $1}')
    tx_bytes=$(echo "$stats" | awk '{print $2}')
    
    echo "$timestamp,$INTERFACE,$rx_bytes,$tx_bytes" >> "$LOG_FILE"
    
    sleep "$INTERVAL"
done

15.1.6 Integration von curl und wget

Die Tools curl und wget bieten fortgeschrittenere Funktionen für HTTP/HTTPS und andere Protokolle:

#!/bin/bash

# Grundlegende GET-Anfrage
curl -s https://api.example.com/status

# POST-Anfrage mit JSON-Daten
curl -X POST \
     -H "Content-Type: application/json" \
     -d '{"key": "value"}' \
     https://api.example.com/update

# Datei herunterladen mit Fortschrittsanzeige
wget --progress=bar https://example.com/large-file.tar.gz

# Website-Zustand überwachen
check_website() {
    local url="$1"
    local expected_status="$2"
    
    # HTTP-Statuscode abrufen
    status=$(curl -s -o /dev/null -w "%{http_code}" "$url")
    
    if [[ "$status" == "$expected_status" ]]; then
        echo "OK: $url returned status $status"
        return 0
    else
        echo "FEHLER: $url returned status $status (expected $expected_status)"
        return 1
    fi
}

# Verwendung
check_website "https://example.com" "200"

15.1.7 Netzwerkdatenverarbeitung mit Bash

Ein typisches Anwendungsszenario ist die Verarbeitung von Serverprotokollen zur Erkennung von Angriffen:

#!/bin/bash

LOG_FILE="/var/log/auth.log"
THRESHOLD=5  # Schwellenwert für fehlgeschlagene Anmeldeversuche
BAN_SCRIPT="/usr/local/bin/ban_ip.sh"

# Fehlgeschlagene SSH-Anmeldeversuche zählen
failed_attempts=$(grep "Failed password" "$LOG_FILE" | awk '{print $11}' | sort | uniq -c)

echo "$failed_attempts" | while read count ip; do
    if [[ $count -ge $THRESHOLD ]]; then
        echo "IP $ip hat $count fehlgeschlagene Anmeldeversuche"
        
        # Optional: IP-Adresse blockieren
        if [[ -x "$BAN_SCRIPT" ]]; then
            "$BAN_SCRIPT" "$ip"
            echo "IP $ip wurde blockiert"
        fi
    fi
done

15.1.8 Optimierung von Netzwerkoperationen

Bei der Implementierung von Netzwerkoperationen in Shell-Skripten sollten Sie folgende Best Practices berücksichtigen:

  1. Timeouts implementieren: Vermeiden Sie hängende Prozesse durch angemessene Timeouts.
  2. Fehlerbehandlung: Fangen Sie Netzwerkfehler ab und reagieren Sie entsprechend.
  3. Parallel verarbeiten: Nutzen Sie Hintergrundprozesse für parallele Verarbeitung, aber begrenzen Sie deren Anzahl.
  4. Ressourcennutzung überwachen: Achten Sie auf CPU- und Speicherverbrauch bei intensiven Netzwerkoperationen.
  5. Logrotation: Implementieren Sie eine Rotation für Protokolldateien, um Plattenplatz zu sparen.

Diese grundlegenden Netzwerkbefehle und Techniken bilden das Fundament für die Entwicklung leistungsfähiger Netzwerk-Monitoring- und Automatisierungsskripte in Bash. Mit zunehmender Komplexität der Anforderungen empfiehlt es sich jedoch, spezialisierte Tools wie Python oder spezialisierte Netzwerk-Monitoring-Systeme in Betracht zu ziehen.

15.2 HTTP-Anfragen mit curl und wget

Die Tools curl und wget sind mächtige Kommandozeilenprogramme, die für den Transfer von Daten über verschiedene Netzwerkprotokolle entwickelt wurden. Beide eignen sich hervorragend für die Integration in Shell-Skripte und ergänzen die Netzwerkfähigkeiten der Bash erheblich. Während sie sich in einigen Funktionalitäten überschneiden, haben sie unterschiedliche Stärken und Einsatzgebiete.

15.2.1 Grundlagen von curl

curl (Client URL) ist ein vielseitiges Werkzeug zum Übertragen von Daten mit URL-Syntax. Es unterstützt zahlreiche Protokolle, darunter HTTP, HTTPS, FTP, FTPS, SCP, SFTP, LDAP und mehr.

15.2.1.1 Einfache GET-Anfragen

# Grundlegende Anfrage - Ausgabe auf stdout
curl https://example.com

# Stille Ausgabe (nur Inhalt, keine Fortschrittsanzeige)
curl -s https://example.com

# Ausgabe in eine Datei umleiten
curl -o output.html https://example.com

# Datei mit Originalname speichern
curl -O https://example.com/document.pdf

15.2.1.2 Arbeiten mit HTTP-Headern

# Header einer Anfrage anzeigen
curl -I https://example.com

# Benutzerdefinierte Header senden
curl -H "User-Agent: MeinScript/1.0" \
     -H "Accept: application/json" \
     https://api.example.com
     
# Nur Response-Header anzeigen
curl --head https://example.com

15.2.1.3 HTTP-Methoden und Anfragekörper

# POST-Anfrage mit Formular-Daten
curl -X POST \
     -d "username=benutzer&password=geheim" \
     https://example.com/login

# POST mit JSON-Daten
curl -X POST \
     -H "Content-Type: application/json" \
     -d '{"username":"benutzer","password":"geheim"}' \
     https://api.example.com/login
     
# PUT-Anfrage zum Aktualisieren von Ressourcen
curl -X PUT \
     -H "Content-Type: application/json" \
     -d '{"status":"active"}' \
     https://api.example.com/users/123

# DELETE-Anfrage
curl -X DELETE https://api.example.com/users/123

15.2.1.4 Authentifizierung

# Basic Authentication
curl -u username:password https://example.com/secured

# OAuth2 Bearer Token
curl -H "Authorization: Bearer mein_access_token" \
     https://api.example.com/protected-resource

15.2.1.5 Cookies und Sessions

# Cookies speichern
curl -c cookies.txt https://example.com/login

# Gespeicherte Cookies verwenden
curl -b cookies.txt https://example.com/dashboard

# Cookies in derselben Session verwenden
curl -b cookies.txt -c cookies.txt https://example.com/next-page

15.2.1.6 Erweiterte curl-Optionen

# Maximale Übertragungszeit festlegen
curl --max-time 10 https://example.com

# HTTP-Weiterleitungen folgen
curl -L https://example.com/redirect

# Proxy verwenden
curl -x http://proxy.example.com:8080 https://target.com

# Zertifikatsprüfung überspringen (nur zu Testzwecken!)
curl -k https://self-signed.example.com

# Ausgabe mit detaillierten Informationen
curl -v https://example.com

# Nur bestimmte HTTP-Statuscode akzeptieren
curl --write-out "%{http_code}\n" --silent --output /dev/null https://example.com

15.2.2 Grundlagen von wget

wget ist spezialisiert auf das nicht-interaktive Herunterladen von Dateien. Es ist besonders nützlich für große Downloads und gespiegelte Websites.

15.2.2.1 Einfache Downloads

# Grundlegende Verwendung
wget https://example.com/file.zip

# Ausgabedatei umbenennen
wget -O neuername.zip https://example.com/file.zip

# Im Hintergrund ausführen
wget -b https://example.com/large-file.iso

15.2.2.2 Rekursives Herunterladen

# Website spiegeln (mit Einschränkungen)
wget -m -p -k https://example.com

# Rekursiv mit Tiefenbegrenzung
wget -r -l 2 https://example.com

15.2.2.3 Einschränken und Steuern von Downloads

# Bandbreitenbegrenzung (auf 100KB/s)
wget --limit-rate=100k https://example.com/large-file.iso

# Fortsetzung unterbrochener Downloads
wget -c https://example.com/large-file.iso

# Retry bei Fehler
wget -t 5 https://unreliable-server.com/file.zip

15.2.2.4 Authentifizierung mit wget

# Basic Auth
wget --user=username --password=password https://example.com/secure

# Verwenden von .netrc für Anmeldedaten
wget --use-askpass https://example.com/secure

15.2.3 Praktische Anwendungsfälle in Shell-Skripten

15.2.3.1 API-Integration für Wetterabfragen

#!/bin/bash

API_KEY="your_api_key"
CITY="Berlin"
OUTPUT_FILE="/tmp/weather_report.json"

# OpenWeatherMap API abfragen
curl -s "https://api.openweathermap.org/data/2.5/weather?q=${CITY}&appid=${API_KEY}&units=metric" > "$OUTPUT_FILE"

# Daten extrahieren mit jq (wenn verfügbar)
if command -v jq &>/dev/null; then
    temperature=$(jq '.main.temp' "$OUTPUT_FILE")
    description=$(jq -r '.weather[0].description' "$OUTPUT_FILE")
    echo "Wetter in $CITY: $description, $temperature°C"
else
    # Alternativ mit grep und awk
    temperature=$(grep -o '"temp":[^,]*' "$OUTPUT_FILE" | awk -F: '{print $2}')
    echo "Temperatur in $CITY: $temperature°C"
fi

15.2.3.2 Website-Monitoring

#!/bin/bash

# Zu überwachende Websites
SITES=("https://example.com" "https://example.org" "https://example.net")
LOG_FILE="/var/log/website_monitor.log"
ALERT_SCRIPT="/path/to/alert.sh"

check_website() {
    local url="$1"
    local start_time=$(date +%s.%N)
    
    # HTTP-Statuscode und Antwortzeit messen
    local response=$(curl -s -w "%{http_code},%{time_total}" -o /dev/null "$url")
    local status_code=$(echo "$response" | cut -d, -f1)
    local response_time=$(echo "$response" | cut -d, -f2)
    
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    
    if [ "$status_code" -eq 200 ]; then
        echo "$timestamp - $url - OK (Status: $status_code, Zeit: ${response_time}s)" >> "$LOG_FILE"
        return 0
    else
        echo "$timestamp - $url - FEHLER (Status: $status_code, Zeit: ${response_time}s)" >> "$LOG_FILE"
        
        # Alarmierung, wenn Status nicht 200 ist
        if [ -x "$ALERT_SCRIPT" ]; then
            "$ALERT_SCRIPT" "$url" "$status_code" "$response_time"
        fi
        return 1
    fi
}

# Jede Website prüfen
for site in "${SITES[@]}"; do
    check_website "$site"
done

15.2.3.3 Automatisiertes Herunterladen und Verarbeiten

#!/bin/bash

DOWNLOAD_URL="https://example.com/daily-report.csv"
DOWNLOAD_DIR="/var/data/reports"
ARCHIVE_DIR="/var/data/archive"
LOG_FILE="/var/log/download.log"

# Sicherstellen, dass die Verzeichnisse existieren
mkdir -p "$DOWNLOAD_DIR" "$ARCHIVE_DIR"

# Aktuelles Datum für Dateinamen
DATE=$(date +%Y-%m-%d)
FILENAME="report-$DATE.csv"

log() {
    echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$LOG_FILE"
}

# Download mit wget
log "Starte Download von $DOWNLOAD_URL"
if wget -q --tries=3 --timeout=15 -O "$DOWNLOAD_DIR/$FILENAME" "$DOWNLOAD_URL"; then
    log "Download erfolgreich: $FILENAME"
    
    # Datei verarbeiten
    RECORD_COUNT=$(wc -l < "$DOWNLOAD_DIR/$FILENAME")
    log "Anzahl der Datensätze: $RECORD_COUNT"
    
    # Archivierung
    gzip -c "$DOWNLOAD_DIR/$FILENAME" > "$ARCHIVE_DIR/$FILENAME.gz"
    log "Datei archiviert: $ARCHIVE_DIR/$FILENAME.gz"
    
    # Weitere Verarbeitung hier...
else
    log "Download fehlgeschlagen mit Fehlercode $?"
    exit 1
fi

15.2.3.4 RESTful API-Client

#!/bin/bash

API_BASE="https://api.example.com/v1"
TOKEN="your_access_token"
CONFIG_FILE=".api_config"

# Konfigurationsdatei laden, falls vorhanden
if [[ -f "$CONFIG_FILE" ]]; then
    source "$CONFIG_FILE"
fi

# API-Anfrage-Funktion mit curl
api_request() {
    local method="$1"
    local endpoint="$2"
    local data="$3"
    
    # Header für alle Anfragen
    local headers=(
        -H "Authorization: Bearer $TOKEN"
        -H "Content-Type: application/json"
        -H "Accept: application/json"
    )
    
    # Anfrage ausführen
    if [[ -n "$data" ]]; then
        curl -s -X "$method" "${headers[@]}" -d "$data" "${API_BASE}${endpoint}"
    else
        curl -s -X "$method" "${headers[@]}" "${API_BASE}${endpoint}"
    fi
}

# Verschiedene API-Funktionen
get_resource() {
    local resource_id="$1"
    api_request "GET" "/resources/$resource_id"
}

create_resource() {
    local resource_data="$1"
    api_request "POST" "/resources" "$resource_data"
}

update_resource() {
    local resource_id="$1"
    local resource_data="$2"
    api_request "PUT" "/resources/$resource_id" "$resource_data"
}

delete_resource() {
    local resource_id="$1"
    api_request "DELETE" "/resources/$resource_id"
}

# Beispielverwendung
case "$1" in
    "list")
        api_request "GET" "/resources" | jq '.'
        ;;
    "get")
        get_resource "$2" | jq '.'
        ;;
    "create")
        create_resource "$2" | jq '.'
        ;;
    "update")
        update_resource "$2" "$3" | jq '.'
        ;;
    "delete")
        delete_resource "$2"
        ;;
    *)
        echo "Verwendung: $0 {list|get|create|update|delete} [id] [json_data]"
        exit 1
        ;;
esac

15.2.4 Sicherheitshinweise

Bei der Verwendung von curl und wget in Skripten sollten folgende Sicherheitsaspekte beachtet werden:

  1. Behandlung von Anmeldedaten: Speichern Sie Passwörter niemals im Klartext in Skripten. Verwenden Sie stattdessen Umgebungsvariablen, sichere Speicher oder Konfigurationsdateien mit eingeschränkten Berechtigungen.

  2. Zertifikatsprüfung: Vermeiden Sie -k oder --no-check-certificate in Produktionsumgebungen. Eine deaktivierte Zertifikatsprüfung macht Ihre Verbindung anfällig für Man-in-the-Middle-Angriffe.

  3. Eingabevalidierung: Überprüfen Sie alle vom Benutzer bereitgestellten Eingaben, die in URLs oder Anfragedaten verwendet werden.

  4. Ausgabevalidierung: Behandeln Sie Antworten von unbekannten oder nicht vertrauenswürdigen Servern mit Vorsicht. Überprüfen Sie Daten, bevor Sie sie weiterverarbeiten.

  5. Ratelimiting-Beachtung: Respektieren Sie die Nutzungsbeschränkungen der APIs, die Sie verwenden, um Sperren zu vermeiden.

15.2.5 Leistungsoptimierung

Für rechenintensive Operationen mit vielen HTTP-Anfragen:

#!/bin/bash

# URLs zum Abrufen
mapfile -t URLS < urls.txt

# Maximale Anzahl gleichzeitiger Prozesse
MAX_PROCS=10
current_procs=0

for url in "${URLS[@]}"; do
    # Warten, wenn die maximale Anzahl erreicht ist
    if [[ $current_procs -ge $MAX_PROCS ]]; then
        wait -n  # Warten auf Beendigung eines Prozesses
        ((current_procs--))
    fi
    
    # Anfrage im Hintergrund starten
    (curl -s "$url" > "$(basename "$url").html") &
    ((current_procs++))
done

# Auf verbleibende Prozesse warten
wait

Durch die Kombination von curl, wget und Shell-Skripting können Sie leistungsstarke Netzwerkanwendungen erstellen, die HTTP-Anfragen effizient verarbeiten und komplexe Aufgaben automatisieren. Diese Tools sind unverzichtbar für Systemadministratoren und Entwickler, die mit Webanwendungen, APIs oder Datentransfers arbeiten.

15.3 Arbeiten mit APIs und Web-Services

Die Integration von APIs und Web-Services in Shell-Skripte ermöglicht die Automatisierung zahlreicher netzwerkbasierter Aufgaben und die Erstellung leistungsfähiger Workflows. In diesem Abschnitt werden wir uns mit den Grundlagen der API-Kommunikation, REST-Prinzipien und der Verarbeitung von Antwortformaten wie JSON und XML befassen.

15.3.1 Grundlegende API-Konzepte

APIs (Application Programming Interfaces) definieren Schnittstellen, über die verschiedene Softwaresysteme miteinander kommunizieren können. Web-APIs nutzen typischerweise das HTTP-Protokoll und sind in modernen IT-Umgebungen allgegenwärtig.

15.3.1.1 Arten von Web-APIs

  1. REST (Representational State Transfer): Ein architektonischer Stil, bei dem Ressourcen durch URIs identifiziert werden und Standard-HTTP-Methoden (GET, POST, PUT, DELETE) zur Manipulation dieser Ressourcen verwendet werden.

  2. SOAP (Simple Object Access Protocol): Ein Protokoll auf XML-Basis, das komplexere Operationen und formale Verträge zwischen Client und Server unterstützt.

  3. GraphQL: Eine moderne Abfragesprache, die es Clients ermöglicht, genau die Daten anzufordern, die sie benötigen.

  4. WebSockets: Ein Protokoll für bidirektionale Kommunikation in Echtzeit.

15.3.2 REST-APIs mit Bash

REST-APIs sind aufgrund ihrer Einfachheit und Standardisierung besonders gut für die Integration in Shell-Skripte geeignet.

15.3.2.1 HTTP-Methoden und ihre Verwendung

# GET: Daten abrufen
curl -s "https://api.example.com/users/123"

# POST: Neue Ressource erstellen
curl -s -X POST \
     -H "Content-Type: application/json" \
     -d '{"name": "Max Mustermann", "email": "max@example.com"}' \
     "https://api.example.com/users"

# PUT: Bestehende Ressource aktualisieren
curl -s -X PUT \
     -H "Content-Type: application/json" \
     -d '{"name": "Max Meier", "email": "max@example.com"}' \
     "https://api.example.com/users/123"

# PATCH: Teilweise Aktualisierung einer Ressource
curl -s -X PATCH \
     -H "Content-Type: application/json" \
     -d '{"email": "neuemail@example.com"}' \
     "https://api.example.com/users/123"

# DELETE: Ressource löschen
curl -s -X DELETE "https://api.example.com/users/123"

15.3.3 Authentifizierung bei APIs

Die meisten APIs erfordern eine Form der Authentifizierung. Hier sind die gängigsten Methoden:

15.3.3.1 API-Keys

#!/bin/bash

API_KEY="Ihr_API_Key_hier"
API_ENDPOINT="https://api.example.com/data"

# API-Key als Query-Parameter
curl -s "${API_ENDPOINT}?api_key=${API_KEY}"

# API-Key als Header
curl -s -H "X-API-Key: ${API_KEY}" "${API_ENDPOINT}"

15.3.3.2 OAuth 2.0

#!/bin/bash

# Konfiguration
CLIENT_ID="Ihre_Client_ID"
CLIENT_SECRET="Ihr_Client_Secret"
TOKEN_URL="https://auth.example.com/oauth/token"
API_ENDPOINT="https://api.example.com/protected-resource"

# Token anfordern
token_response=$(curl -s -X POST "${TOKEN_URL}" \
                      -d "grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}")

# Token extrahieren (erfordert jq für JSON-Verarbeitung)
access_token=$(echo "${token_response}" | jq -r '.access_token')

# API mit Bearer-Token aufrufen
curl -s -H "Authorization: Bearer ${access_token}" "${API_ENDPOINT}"

15.3.3.3 Basic Authentication

#!/bin/bash

USERNAME="benutzer"
PASSWORD="passwort"
API_ENDPOINT="https://api.example.com/secured-resource"

# Basic Auth mit curl
curl -s -u "${USERNAME}:${PASSWORD}" "${API_ENDPOINT}"

# Alternativ mit Header
auth_header=$(echo -n "${USERNAME}:${PASSWORD}" | base64)
curl -s -H "Authorization: Basic ${auth_header}" "${API_ENDPOINT}"

15.3.4 Verarbeitung von JSON-Antworten

JSON (JavaScript Object Notation) ist das häufigste Datenformat für moderne APIs. Die Verarbeitung von JSON in Bash kann mit verschiedenen Tools erfolgen:

15.3.4.1 Verarbeitung mit jq

jq ist ein leichtgewichtiger, flexibler JSON-Prozessor für die Kommandozeile:

#!/bin/bash

# Beispiel: Abrufen von Wetterdaten
API_KEY="Ihr_Wetter_API_Key"
CITY="Berlin"
WEATHER_API="https://api.openweathermap.org/data/2.5/weather?q=${CITY}&units=metric&appid=${API_KEY}"

# API aufrufen und JSON verarbeiten
weather_data=$(curl -s "${WEATHER_API}")

# Extrahieren spezifischer Werte mit jq
temperature=$(echo "${weather_data}" | jq -r '.main.temp')
weather_desc=$(echo "${weather_data}" | jq -r '.weather[0].description')
humidity=$(echo "${weather_data}" | jq -r '.main.humidity')

echo "Wetter in ${CITY}:"
echo "Temperatur: ${temperature}°C"
echo "Beschreibung: ${weather_desc}"
echo "Luftfeuchtigkeit: ${humidity}%"

# Filtern von Arrays
echo "${weather_data}" | jq '.weather[] | {description: .description, icon: .icon}'

# Transformieren von Daten
echo "${weather_data}" | jq '{
    city: .name,
    country: .sys.country,
    temperature: .main.temp,
    conditions: .weather[0].main,
    wind_speed: .wind.speed
}'

15.3.4.2 Verarbeitung ohne externe Tools

Wenn jq nicht verfügbar ist, können Sie auf andere Tools zurückgreifen:

#!/bin/bash

# API aufrufen
response=$(curl -s "https://api.example.com/data")

# Einfache Textverarbeitung für spezifische Werte
# (Hinweis: Diese Methode ist weniger robust als jq)
name=$(echo "${response}" | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
id=$(echo "${response}" | grep -o '"id":[0-9]*' | cut -d':' -f2)

echo "Name: ${name}, ID: ${id}"

# Alternativ mit Python (falls verfügbar)
python_extract() {
    python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
print(data$1)
"
}

echo "${response}" | python_extract "['name']"

15.3.5 Verarbeitung von XML-Antworten

Obwohl JSON heute häufiger ist, verwenden viele ältere oder unternehmensinterne APIs noch XML:

#!/bin/bash

# XML-API aufrufen
xml_response=$(curl -s "https://api.example.com/xml-endpoint")

# Mit xmllint (aus libxml2-utils) verarbeiten
if command -v xmllint &>/dev/null; then
    # XPath-Abfrage verwenden
    value=$(echo "${xml_response}" | xmllint --xpath "//root/element/text()" -)
    echo "Extrahierter Wert: ${value}"
fi

# Mit grep und sed (einfachere Alternative)
element_value=$(echo "${xml_response}" | grep -o "<element>[^<]*</element>" | sed -e 's/<element>\(.*\)<\/element>/\1/')
echo "Element-Wert: ${element_value}"

15.3.6 Praktisches Beispiel: GitHub API-Integration

Das folgende Beispiel zeigt, wie Sie die GitHub REST API in einem Shell-Skript verwenden können:

#!/bin/bash

# Konfiguration
GITHUB_TOKEN="Ihr_GitHub_Token"  # Persönliches Access-Token
GITHUB_API="https://api.github.com"
REPO_OWNER="octocat"
REPO_NAME="hello-world"

# Funktion für GitHub API-Aufrufe
github_api() {
    local endpoint="$1"
    local method="${2:-GET}"
    local data="$3"
    
    local headers=(
        -H "Authorization: token ${GITHUB_TOKEN}"
        -H "Accept: application/vnd.github.v3+json"
    )
    
    if [[ -n "${data}" ]]; then
        curl -s -X "${method}" "${headers[@]}" -d "${data}" "${GITHUB_API}${endpoint}"
    else
        curl -s -X "${method}" "${headers[@]}" "${GITHUB_API}${endpoint}"
    fi
}

# Repository-Informationen abrufen
repo_info=$(github_api "/repos/${REPO_OWNER}/${REPO_NAME}")
repo_name=$(echo "${repo_info}" | jq -r '.name')
repo_stars=$(echo "${repo_info}" | jq -r '.stargazers_count')
repo_forks=$(echo "${repo_info}" | jq -r '.forks_count')

echo "Repository: ${repo_name}"
echo "Stars: ${repo_stars}"
echo "Forks: ${repo_forks}"

# Offene Issues auflisten
issues=$(github_api "/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=open")
echo -e "\nOffene Issues:"
echo "${issues}" | jq -r '.[] | "- #\(.number): \(.title)"'

# Einen neuen Issue erstellen
create_issue() {
    local title="$1"
    local body="$2"
    
    local issue_data=$(jq -n \
                        --arg title "${title}" \
                        --arg body "${body}" \
                        '{title: $title, body: $body}')
    
    github_api "/repos/${REPO_OWNER}/${REPO_NAME}/issues" "POST" "${issue_data}"
}

# Beispielaufruf (auskommentiert)
# new_issue=$(create_issue "API Test Issue" "Dieses Issue wurde automatisch erstellt.")
# echo "Neuer Issue erstellt: #$(echo "${new_issue}" | jq -r '.number')"

15.3.7 Umgang mit Paginierung

Viele APIs begrenzen die Anzahl der Ergebnisse pro Anfrage und verwenden Paginierung:

#!/bin/bash

# Konfiguration
API_ENDPOINT="https://api.example.com/resources"
API_KEY="Ihr_API_Key"
PER_PAGE=100

# Funktion zum Abrufen aller Seiten
fetch_all_pages() {
    local endpoint="$1"
    local page=1
    local all_results="[]"
    
    while true; do
        local url="${endpoint}?page=${page}&per_page=${PER_PAGE}&api_key=${API_KEY}"
        local response=$(curl -s "${url}")
        
        # Ergebnisse dieser Seite extrahieren
        local items=$(echo "${response}" | jq -r '.items')
        local count=$(echo "${items}" | jq 'length')
        
        # Keine weiteren Ergebnisse
        if [[ "${count}" -eq 0 ]]; then
            break
        fi
        
        # Ergebnisse zusammenführen
        all_results=$(echo "${all_results}" | jq --argjson new "${items}" '. + $new')
        
        # Nächste Seite
        ((page++))
    done
    
    echo "${all_results}"
}

# Alle Ressourcen abrufen
all_resources=$(fetch_all_pages "${API_ENDPOINT}")
echo "Insgesamt $(echo "${all_resources}" | jq 'length') Ressourcen gefunden."

# Verarbeiten der Ergebnisse
echo "${all_resources}" | jq -r '.[] | .name'

15.3.8 Fehlerbehandlung und Ratenbegrenzung

Professionelle API-Integrationen erfordern robuste Fehlerbehandlung und Beachtung von Ratenbegrenzungen:

#!/bin/bash

# Konfiguration
API_ENDPOINT="https://api.example.com/data"
MAX_RETRIES=3
RETRY_DELAY=5  # Sekunden

# Funktion für API-Aufrufe mit Fehlerbehandlung
api_request() {
    local url="$1"
    local retry_count=0
    
    while [[ ${retry_count} -lt ${MAX_RETRIES} ]]; do
        # API aufrufen und Statuscode speichern
        response=$(curl -s -w "%{http_code}" -o /tmp/api_response.json "${url}")
        status_code=$response
        
        # Erfolg
        if [[ ${status_code} -ge 200 && ${status_code} -lt 300 ]]; then
            cat /tmp/api_response.json
            return 0
        fi
        
        # Ratenbegrenzung (429 Too Many Requests)
        if [[ ${status_code} -eq 429 ]]; then
            # Retry-After Header auslesen (falls vorhanden)
            retry_after=$(curl -s -I "${url}" | grep -i "retry-after" | awk '{print $2}' | tr -d '\r')
            
            if [[ -n "${retry_after}" ]]; then
                sleep "${retry_after}"
            else
                # Exponentielles Backoff
                sleep $((RETRY_DELAY * 2**retry_count))
            fi
        # Andere Fehler
        else
            echo "Fehler: API gab Statuscode ${status_code} zurück" >&2
            echo "Antwort: $(cat /tmp/api_response.json)" >&2
            
            # Bei bestimmten Fehlern nicht wiederholen
            if [[ ${status_code} -eq 400 || ${status_code} -eq 401 || ${status_code} -eq 403 ]]; then
                return 1
            fi
            
            sleep "${RETRY_DELAY}"
        fi
        
        ((retry_count++))
    done
    
    echo "Maximale Anzahl von Wiederholungen erreicht" >&2
    return 1
}

# Verwendung
data=$(api_request "${API_ENDPOINT}")
if [[ $? -eq 0 ]]; then
    echo "Daten erfolgreich abgerufen"
    echo "${data}" | jq .
else
    echo "Fehler beim Abrufen der Daten"
fi

15.3.9 Polling und Webhooks

Für Langzeitoperationen bieten viele APIs asynchrone Verarbeitungsmethoden:

15.3.9.1 Polling-Beispiel

#!/bin/bash

# Konfiguration
API_BASE="https://api.example.com"
API_KEY="Ihr_API_Key"
POLL_INTERVAL=5  # Sekunden
MAX_POLLS=60     # Maximal 5 Minuten warten

# Starten einer langläufigen Operation
start_job() {
    curl -s -X POST \
         -H "Authorization: Bearer ${API_KEY}" \
         -H "Content-Type: application/json" \
         -d '{"parameters": {"input": "data"}}' \
         "${API_BASE}/jobs"
}

# Status einer Operation abfragen
check_job_status() {
    local job_id="$1"
    
    curl -s -H "Authorization: Bearer ${API_KEY}" \
         "${API_BASE}/jobs/${job_id}"
}

# Job starten
job_response=$(start_job)
job_id=$(echo "${job_response}" | jq -r '.id')
echo "Job gestartet mit ID: ${job_id}"

# Polling bis zur Fertigstellung
poll_count=0
while [[ ${poll_count} -lt ${MAX_POLLS} ]]; do
    job_status=$(check_job_status "${job_id}")
    status=$(echo "${job_status}" | jq -r '.status')
    
    echo "Job-Status: ${status}"
    
    if [[ "${status}" == "completed" ]]; then
        result=$(echo "${job_status}" | jq -r '.result')
        echo "Job abgeschlossen! Ergebnis: ${result}"
        break
    elif [[ "${status}" == "failed" ]]; then
        error=$(echo "${job_status}" | jq -r '.error')
        echo "Job fehlgeschlagen: ${error}" >&2
        exit 1
    fi
    
    ((poll_count++))
    sleep "${POLL_INTERVAL}"
done

if [[ ${poll_count} -eq ${MAX_POLLS} ]]; then
    echo "Zeitüberschreitung beim Warten auf Jobabschluss" >&2
    exit 1
fi

15.3.9.2 Lokaler Webhook-Empfänger

Für Testzwecke kann ein einfacher Webhook-Empfänger mit netcat oder socat eingerichtet werden:

#!/bin/bash

# Einfacher HTTP-Server auf Port 8080
echo "Starte Webhook-Empfänger auf Port 8080..."

process_webhook() {
    local request="$1"
    local body=$(echo "${request}" | awk 'BEGIN{RS="\r\n\r\n"} {print $0}' | tail -n 1)
    
    echo "Webhook empfangen: ${body}"
    
    # Verarbeitung hier...
    if echo "${body}" | jq -e '.event == "completed"' &>/dev/null; then
        echo "Job abgeschlossen!"
    fi
}

while true; do
    response="HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"
    request=$(nc -l 8080)
    echo "${response}" | nc -l 8080 &
    
    process_webhook "${request}"
done

15.3.10 Zusammenführung mehrerer APIs

Komplexe Automatisierungen erfordern oft die Integration mehrerer APIs:

#!/bin/bash

# Konfiguration
WEATHER_API_KEY="Wetter_API_Key"
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXX/YYY/ZZZ"
CITY="Berlin"

# Wetterdaten abrufen
get_weather() {
    local city="$1"
    
    curl -s "https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${WEATHER_API_KEY}"
}

# Nachricht an Slack senden
send_slack_message() {
    local message="$1"
    
    curl -s -X POST \
         -H "Content-Type: application/json" \
         -d "{\"text\":\"${message}\"}" \
         "${SLACK_WEBHOOK_URL}"
}

# Wetterwarnung erstellen
create_weather_alert() {
    # Wetter abrufen
    weather_data=$(get_weather "${CITY}")
    
    # Daten extrahieren
    temp=$(echo "${weather_data}" | jq -r '.main.temp')
    conditions=$(echo "${weather_data}" | jq -r '.weather[0].main')
    
    # Nachricht formatieren
    message="Wetterbericht für ${CITY}: ${temp}°C, ${conditions}"
    
    # Warnungen basierend auf Bedingungen
    if (( $(echo "${temp} > 30" | bc -l) )); then
        message="${message}\n⚠️ WARNUNG: Extreme Hitze! Bitte viel trinken und Schatten suchen."
    elif (( $(echo "${temp} < 0" | bc -l) )); then
        message="${message}\n⚠️ WARNUNG: Frost! Bitte auf mögliche Glätte achten."
    fi
    
    if [[ "${conditions}" == "Thunderstorm" ]]; then
        message="${message}\n⚠️ WARNUNG: Gewitter! Bitte Aktivitäten im Freien einschränken."
    elif [[ "${conditions}" == "Rain" && $(echo "${weather_data}" | jq -r '.rain."3h"') > 10 ]]; then
        message="${message}\n⚠️ WARNUNG: Starker Regen! Mögliche Überschwemmungsgefahr."
    fi
    
    # An Slack senden
    send_slack_message "${message}"
}

# Ausführen
create_weather_alert

15.3.11 Best Practices für API-Integration in Bash

  1. Verwendung von Konfigurationsdateien: Speichern Sie API-Schlüssel und -Endpunkte in separaten Konfigurationsdateien mit restriktiven Berechtigungen.

  2. Tokenablauf berücksichtigen: Implementieren Sie eine Logik zur Erneuerung abgelaufener Tokens.

  3. Fehlerbehandlung: Implementieren Sie robuste Fehlerbehandlung mit Logging und Benachrichtigungen.

  4. Ratenbegrenzungen respektieren: Implementieren Sie Backoff-Strategien, um Ratenbegrenzungen einzuhalten.

  5. Parallele Verarbeitung: Nutzen Sie Hintergrundprozesse für parallele API-Aufrufe, aber mit Begrenzung der gleichzeitigen Anfragen.

  6. Idempotente Operationen: Gestalten Sie Skripte so, dass sie mehrfach ausgeführt werden können, ohne unerwünschte Nebeneffekte zu verursachen.

  7. Caching: Implementieren Sie Caching, um unnötige API-Aufrufe zu vermeiden.

  8. Monitoring: Führen Sie Metriken zu API-Aufrufen und Erfolgs-/Fehlerraten.

Die Integration von APIs in Shell-Skripte erschließt eine enorme Bandbreite an Automatisierungsmöglichkeiten. Von einfachen Datenabfragen bis hin zu komplexen Workflows können Shell-Skripte als effektive Bindemittel zwischen verschiedenen Diensten und Systemen dienen. Durch die Beachtung der Best Practices und den Einsatz der richtigen Tools können selbst komplexe API-Integrationen zuverlässig und wartbar gestaltet werden.

15.4 SSH-Automatisierung und Remote-Ausführung

SSH (Secure Shell) ist ein kryptografisches Netzwerkprotokoll, das sichere Kommunikation über unsichere Netzwerke ermöglicht. In Kombination mit Bash-Skripten wird SSH zu einem mächtigen Werkzeug für die Automatisierung von Aufgaben auf entfernten Systemen. Dieser Abschnitt behandelt die Grundlagen der SSH-Automatisierung, die sichere Ausführung von Befehlen auf entfernten Rechnern und fortgeschrittene Techniken für die effiziente Verwaltung verteilter Systeme.

15.4.1 SSH-Grundlagen für die Automatisierung

15.4.1.1 SSH-Befehlssyntax

Die grundlegende Syntax für SSH-Verbindungen ist:

ssh [Optionen] [Benutzer@]Hostname [Befehl]

Für Automatisierungszwecke sind folgende Optionen besonders nützlich:

# Ausführen eines einzelnen Befehls auf einem entfernten Host
ssh benutzer@server "ls -la /var/log"

# Nicht-interaktiver Modus (vermeidet Hängen bei Problemen)
ssh -n benutzer@server "uptime"

# Batch-Modus (deaktiviert alle Abfragen)
ssh -n -o BatchMode=yes benutzer@server "df -h"

# Verbindungstimeout setzen
ssh -o ConnectTimeout=10 benutzer@server "uptime"

15.4.1.2 Passwortlose Authentifizierung einrichten

Die passwortlose Authentifizierung ist der Schlüssel zur SSH-Automatisierung:

# SSH-Schlüsselpaar generieren (falls noch nicht vorhanden)
ssh-keygen -t ed25519 -C "Automatisierungsschlüssel"

# Öffentlichen Schlüssel auf den Zielserver übertragen
ssh-copy-id benutzer@server

# Alternative, wenn ssh-copy-id nicht verfügbar ist
cat ~/.ssh/id_ed25519.pub | ssh benutzer@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

15.4.1.3 SSH-Konfiguration für einfachere Automatisierung

Die Datei ~/.ssh/config kann verwendet werden, um Verbindungsdetails zu speichern und abzukürzen:

# Host-Alias definieren
Host webserver
    HostName 192.168.1.10
    User admin
    Port 2222
    IdentityFile ~/.ssh/webserver_key
    ConnectTimeout 10
    BatchMode yes
    
# Muster für mehrere Server
Host app-*
    User deployer
    IdentityFile ~/.ssh/deployer_key
    StrictHostKeyChecking no

Mit dieser Konfiguration können Sie einfach ssh webserver oder ssh app-1 verwenden, ohne alle Parameter angeben zu müssen.

15.4.2 Einzelne Befehle auf Remote-Systemen ausführen

15.4.2.1 Einfache Befehlsausführung

#!/bin/bash

# Grundlegende Remote-Befehlsausführung
ssh server "hostname && uptime"

# Umgebungsvariablen festlegen
ssh server "export PATH=/usr/local/bin:\$PATH && which python3"

# Interaktive Befehle vermeiden
ssh -n server "nohup long-running-command > output.log 2>&1 &"

15.4.2.2 Befehle mit sudo-Rechten ausführen

#!/bin/bash

# sudo-Befehl auf einem entfernten System ausführen
ssh admin@server "sudo service nginx restart"

# Mit Passwort-Übergabe (vermeiden, wenn möglich)
echo "sudo_password" | ssh admin@server "sudo -S service nginx restart"

# Besser: sudo ohne Passwort für bestimmte Befehle konfigurieren
# (erfordert vorherige Einrichtung der sudoers-Datei)
ssh admin@server "sudo service nginx restart"

15.4.2.3 Umgang mit Exit-Codes und Fehlern

#!/bin/bash

# Exit-Code vom Remote-Befehl erhalten
ssh server "test -d /var/www/html"
if [ $? -eq 0 ]; then
    echo "Verzeichnis existiert"
else
    echo "Verzeichnis existiert nicht"
fi

# Alternative mit direkter Prüfung
if ssh server "test -d /var/www/html"; then
    echo "Verzeichnis existiert"
else
    echo "Verzeichnis existiert nicht"
fi

# Fehlerbehandlung
ssh server "some-command" || {
    echo "Fehler bei der Befehlsausführung auf dem Server" >&2
    exit 1
}

15.4.3 Skripte auf Remote-Systemen ausführen

15.4.3.1 Lokales Skript auf Remote-System ausführen

#!/bin/bash

# Methode 1: Skript über die SSH-Verbindung pipen
cat local_script.sh | ssh server "bash -s"

# Methode 2: Skript mit Argumenten übergeben
cat local_script.sh | ssh server "bash -s -- arg1 arg2"

# Methode 3: Skript hochladen und ausführen
scp local_script.sh server:/tmp/
ssh server "chmod +x /tmp/local_script.sh && /tmp/local_script.sh"

15.4.3.2 Here-Dokument für komplexe Remote-Skripte

#!/bin/bash

ssh server bash << 'EOT'
# Dieser Code läuft auf dem Remote-Server
LOG_DIR="/var/log"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

# Verarbeitung auf Verzeichnisebene
cd "$LOG_DIR" || exit 1
for log_file in *.log; do
    echo "Verarbeite $log_file"
    # Komplexe Verarbeitung hier...
    gzip -c "$log_file" > "/backup/${log_file}-${TIMESTAMP}.gz"
done

# Aufräumen
find "$LOG_DIR" -name "*.log" -type f -mtime +7 -delete
echo "Bereinigung abgeschlossen"
EOT

15.4.3.3 Remote-Ausführung und lokale Verarbeitung kombinieren

#!/bin/bash

# Daten vom Remote-System abrufen und lokal verarbeiten
server_info=$(ssh server "hostname && uptime && df -h")

# Lokale Verarbeitung
server_name=$(echo "$server_info" | head -1)
uptime_info=$(echo "$server_info" | head -2 | tail -1)
disk_usage=$(echo "$server_info" | grep -m 1 "/dev/sda")

echo "Server: $server_name"
echo "Uptime: $uptime_info"
echo "Hauptpartition: $disk_usage"

15.4.4 Dateiübertragung mit SCP und SFTP

15.4.4.1 Grundlegende SCP-Befehle

#!/bin/bash

# Datei auf einen Remote-Server hochladen
scp local_file.txt user@server:/path/to/destination/

# Datei von einem Remote-Server herunterladen
scp user@server:/path/to/remote_file.txt local_directory/

# Verzeichnis rekursiv übertragen
scp -r local_directory/ user@server:/path/to/destination/

# Übertragungsgeschwindigkeit begrenzen (KB/s)
scp -l 1000 large_file.iso user@server:/tmp/

15.4.4.2 Automatisierte Dateiübertragung mit SFTP

#!/bin/bash

# SFTP-Befehle in einem Batchmodus ausführen
sftp -b - user@server << EOF
cd /remote/directory
lcd /local/directory
get file1.txt
get file2.txt
put local_file.txt
bye
EOF

15.4.4.3 Rsync über SSH für effiziente Übertragungen

#!/bin/bash

# Grundlegende Rsync-Übertragung über SSH
rsync -avz -e ssh /local/directory/ user@server:/remote/directory/

# Inkrementelles Backup mit Rsync
rsync -avz --delete -e ssh /local/directory/ user@server:/backup/directory/

# Übertragung mit Bandbreitenbegrenzung
rsync -avz --bwlimit=1000 -e ssh /local/directory/ user@server:/remote/directory/

# Ausschließen bestimmter Dateien
rsync -avz --exclude='*.tmp' --exclude='cache/' -e ssh /local/directory/ user@server:/remote/directory/

15.4.5 Parallele Ausführung auf mehreren Servern

15.4.5.1 Einfache sequentielle Ausführung

#!/bin/bash

SERVERS=("web1" "web2" "web3" "db1" "db2")
COMMAND="uptime"

for server in "${SERVERS[@]}"; do
    echo "=== Führe Befehl auf $server aus ==="
    ssh "$server" "$COMMAND"
done

15.4.5.2 Parallele Ausführung mit Hintergrundprozessen

#!/bin/bash

SERVERS=("web1" "web2" "web3" "db1" "db2")
COMMAND="uptime"
MAX_PARALLEL=3
running=0

for server in "${SERVERS[@]}"; do
    # Maximale Anzahl paralleler Prozesse begrenzen
    if [[ $running -ge $MAX_PARALLEL ]]; then
        wait -n  # Warten auf Beendigung eines Prozesses
        running=$((running - 1))
    fi
    
    # Befehl im Hintergrund ausführen
    (
        echo "=== Ausführung auf $server ===" 
        if ssh -o ConnectTimeout=5 "$server" "$COMMAND"; then
            echo "=== $server: Erfolgreich ==="
        else
            echo "=== $server: Fehler ($?) ===" >&2
        fi
    ) &
    
    running=$((running + 1))
done

# Auf verbleibende Prozesse warten
wait
echo "Alle Befehle abgeschlossen"

15.4.5.3 Fortgeschrittene parallele Ausführung mit Ergebnisverarbeitung

#!/bin/bash

SERVERS=("web1" "web2" "web3" "db1" "db2")
RESULTS_DIR="/tmp/server_results"
mkdir -p "$RESULTS_DIR"

# Funktion zur Ausführung auf einem Server
run_on_server() {
    local server="$1"
    local output_file="$RESULTS_DIR/$server.out"
    
    echo "Starte Ausführung auf $server..."
    
    # Mehrere Befehle auf dem Remote-Server ausführen
    ssh -o ConnectTimeout=5 "$server" << 'EOT' > "$output_file" 2>&1
        hostname
        uptime
        df -h | grep -v tmpfs
        free -m
        grep -c processor /proc/cpuinfo
EOT
    
    local status=$?
    if [[ $status -eq 0 ]]; then
        echo "Ausführung auf $server erfolgreich abgeschlossen"
    else
        echo "Fehler bei der Ausführung auf $server (Exit-Code: $status)" >&2
    fi
    
    return $status
}

# Parallel auf allen Servern ausführen
for server in "${SERVERS[@]}"; do
    run_on_server "$server" &
done

# Auf alle Hintergrundprozesse warten
wait

# Ergebnisse zusammenfassen
echo -e "\n=== Zusammenfassung ===\n"
for server in "${SERVERS[@]}"; do
    result_file="$RESULTS_DIR/$server.out"
    if [[ -f "$result_file" ]]; then
        hostname=$(head -1 "$result_file")
        disk_usage=$(grep -m 1 '/' "$result_file" | awk '{print $5}')
        load=$(grep -o 'load average:.*' "$result_file" | cut -d: -f2 | tr -d ',')
        cpu_count=$(tail -1 "$result_file")
        
        echo "$server ($hostname):"
        echo "  - Festplattenbelegung: $disk_usage"
        echo "  - Load: $load"
        echo "  - CPUs: $cpu_count"
    else
        echo "$server: Keine Ergebnisse (Verbindungsfehler?)"
    fi
done

15.4.6 SSH-Tunneling und Port-Forwarding

15.4.6.1 Lokales Port-Forwarding

#!/bin/bash

# Lokales Port-Forwarding einrichten
# Lokaler Port 8080 wird an den Port 80 des Remote-Servers weitergeleitet
ssh -L 8080:localhost:80 -N -f user@server

# Überprüfen, ob der Tunnel läuft
if ps aux | grep "ssh -L" | grep -v grep > /dev/null; then
    echo "SSH-Tunnel ist aktiv"
else
    echo "SSH-Tunnel konnte nicht eingerichtet werden"
fi

# Skript, das den Tunnel nutzt
curl http://localhost:8080/

# Tunnel beenden (alle passenden SSH-Prozesse)
pkill -f "ssh -L 8080:localhost:80"

15.4.6.2 Entferntes Port-Forwarding

#!/bin/bash

# Entferntes Port-Forwarding einrichten
# Port 8080 auf dem Remote-Server wird an den lokalen Port 80 weitergeleitet
ssh -R 8080:localhost:80 -N -f user@server

# Tunnel-Management
PID_FILE="/tmp/ssh_tunnel.pid"

# Tunnel starten
start_tunnel() {
    ssh -R 8080:localhost:80 -N -f user@server
    echo $! > "$PID_FILE"
    echo "Tunnel gestartet mit PID $(cat "$PID_FILE")"
}

# Tunnel stoppen
stop_tunnel() {
    if [[ -f "$PID_FILE" ]]; then
        kill $(cat "$PID_FILE") 2>/dev/null
        rm "$PID_FILE"
        echo "Tunnel beendet"
    else
        echo "Kein aktiver Tunnel gefunden"
    fi
}

# Tunnelstatus überprüfen
check_tunnel() {
    if [[ -f "$PID_FILE" ]] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
        echo "Tunnel läuft mit PID $(cat "$PID_FILE")"
    else
        echo "Tunnel läuft nicht"
    fi
}

15.4.6.3 SOCKS-Proxy über SSH

#!/bin/bash

# SOCKS-Proxy auf Port 1080 einrichten
ssh -D 1080 -q -C -N -f user@server

# Überprüfen, ob der Proxy läuft
netstat -tulpn | grep 1080

# Beispiel: curl über den SOCKS-Proxy
curl --socks5 localhost:1080 http://example.com/

15.4.7 SSH-Schlüsselverwaltung

15.4.7.1 SSH-Schlüssel automatisch generieren

#!/bin/bash

KEY_TYPE="ed25519"
KEY_NAME="automation_key"
KEY_FILE="$HOME/.ssh/${KEY_NAME}"
KEY_COMMENT="Automatisch generierter Schlüssel für $(whoami)@$(hostname)"

# Prüfen, ob der Schlüssel bereits existiert
if [[ -f "${KEY_FILE}" ]]; then
    echo "Schlüssel existiert bereits: ${KEY_FILE}"
    exit 0
fi

# Schlüssel ohne Passphrase generieren
ssh-keygen -t "$KEY_TYPE" -f "$KEY_FILE" -N "" -C "$KEY_COMMENT"

# Berechtigungen korrigieren
chmod 600 "${KEY_FILE}"
chmod 644 "${KEY_FILE}.pub"

echo "Schlüssel erfolgreich generiert:"
echo "Private Key: ${KEY_FILE}"
echo "Public Key: ${KEY_FILE}.pub"

# Optional: Öffentlichen Schlüssel anzeigen
echo -e "\nÖffentlicher Schlüssel:"
cat "${KEY_FILE}.pub"

15.4.7.2 Verteilung von SSH-Schlüsseln auf mehrere Server

#!/bin/bash

KEY_FILE="$HOME/.ssh/automation_key.pub"
SERVERS=("web1" "web2" "db1" "db2")

# Prüfen, ob der öffentliche Schlüssel existiert
if [[ ! -f "$KEY_FILE" ]]; then
    echo "Fehler: Öffentlicher Schlüssel nicht gefunden: $KEY_FILE" >&2
    exit 1
fi

# Schlüssel auf jeden Server kopieren
for server in "${SERVERS[@]}"; do
    echo "Kopiere Schlüssel auf $server..."
    
    # Methode 1: Mit ssh-copy-id
    if command -v ssh-copy-id >/dev/null; then
        ssh-copy-id -i "$KEY_FILE" "user@$server"
    else
        # Methode 2: Manuelles Kopieren
        cat "$KEY_FILE" | ssh "user@$server" "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
    fi
    
    # Überprüfen, ob der Schlüssel funktioniert
    if ssh -i "${KEY_FILE%.pub}" -o BatchMode=yes "user@$server" "echo 'Schlüsseltest erfolgreich'"; then
        echo "✓ Schlüssel auf $server erfolgreich installiert"
    else
        echo "✗ Fehler beim Testen des Schlüssels auf $server" >&2
    fi
done

15.4.7.3 Automatische SSH-Agent-Verwendung

#!/bin/bash

# SSH-Agent starten, falls noch nicht aktiv
start_ssh_agent() {
    if [[ -z "$SSH_AUTH_SOCK" ]]; then
        eval $(ssh-agent -s)
        echo "SSH-Agent gestartet mit PID $SSH_AGENT_PID"
    else
        echo "SSH-Agent läuft bereits mit Socket $SSH_AUTH_SOCK"
    fi
}

# Schlüssel zum SSH-Agent hinzufügen
add_key_to_agent() {
    local key_file="$1"
    
    if [[ ! -f "$key_file" ]]; then
        echo "Fehler: Schlüsseldatei nicht gefunden: $key_file" >&2
        return 1
    fi
    
    # Prüfen, ob der Schlüssel bereits hinzugefügt wurde
    if ssh-add -l | grep -q "$(ssh-keygen -lf "$key_file" | awk '{print $2}')"; then
        echo "Schlüssel ist bereits im SSH-Agent"
    else
        ssh-add "$key_file"
        echo "Schlüssel hinzugefügt: $key_file"
    fi
}

# Verwendung
start_ssh_agent
add_key_to_agent "$HOME/.ssh/automation_key"

# Automatisierungsbefehle mit SSH-Agent ausführen
ssh server "uptime"

15.4.8 Sicherheit bei der SSH-Automatisierung

15.4.8.1 Einschränkung von SSH-Schlüsseln

In der authorized_keys-Datei können Schlüssel mit Einschränkungen versehen werden:

# Beispiel für einen Eintrag in ~/.ssh/authorized_keys mit Einschränkungen
# Nur bestimmte Befehle erlauben
command="uptime" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... automation@host

# IP-Beschränkungen
from="192.168.1.*" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... automation@host

# Kombination mehrerer Einschränkungen
command="df -h",from="10.0.0.*",no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... automation@host

15.4.8.2 Skript zur sicheren Schlüsselverteilung mit Einschränkungen

#!/bin/bash

KEY_FILE="$HOME/.ssh/automation_key.pub"
SERVER="target-server"
ALLOWED_COMMANDS=("uptime" "df -h" "/usr/local/sbin/backup.sh")
ALLOWED_NETWORKS=("192.168.1.*" "10.0.0.*")

# Öffentlichen Schlüssel einlesen
if [[ ! -f "$KEY_FILE" ]]; then
    echo "Fehler: Öffentlicher Schlüssel nicht gefunden: $KEY_FILE" >&2
    exit 1
fi
public_key=$(cat "$KEY_FILE")

# Einschränkungen erstellen
restrictions=""

# Netzwerk-Einschränkungen
network_restriction="from=\"$(IFS=,; echo "${ALLOWED_NETWORKS[*]}")\""
restrictions="${restrictions}${network_restriction},"

# Befehlseinschränkungen, wenn spezifiziert
if [[ ${#ALLOWED_COMMANDS[@]} -gt 0 ]]; then
    # Mehrere Befehle mit SSH-Wrapper-Skript erlauben
    ssh "user@$SERVER" "cat > /tmp/ssh-wrapper.sh << 'EOF'
#!/bin/bash
# SSH-Befehlswrapper
cmd=\$SSH_ORIGINAL_COMMAND
case \"\$cmd\" in
$(for cmd in "${ALLOWED_COMMANDS[@]}"; do echo "    \"$cmd\") $cmd ;;"; done)
    *) echo \"Befehl nicht erlaubt: \$cmd\" >&2; exit 1 ;;
esac
EOF
    chmod +x /tmp/ssh-wrapper.sh
    sudo mv /tmp/ssh-wrapper.sh /usr/local/bin/ssh-wrapper.sh"
    
    restrictions="${restrictions}command=\"/usr/local/bin/ssh-wrapper.sh\","
fi

# Weitere Einschränkungen
restrictions="${restrictions}no-port-forwarding,no-X11-forwarding,no-agent-forwarding"

# Schlüssel mit Einschränkungen hinzufügen
restricted_key="$restrictions $public_key"
echo "Installiere eingeschränkten Schlüssel auf $SERVER..."
echo "$restricted_key" | ssh "user@$SERVER" "cat >> ~/.ssh/authorized_keys"

echo "Schlüssel mit folgenden Einschränkungen installiert:"
echo "$restrictions"

15.4.8.3 Überwachung von SSH-Verbindungen

#!/bin/bash

# Überwachung aktiver SSH-Sitzungen auf einem Server
SERVER="monitoring-server"

# SSH-Sitzungen abfragen
ssh_sessions=$(ssh "admin@$SERVER" "who | grep -i ssh")

echo "Aktive SSH-Sitzungen auf $SERVER:"
echo "$ssh_sessions"

# Optional: Verdächtige Aktivitäten überprüfen
ssh "admin@$SERVER" "grep 'Failed password' /var/log/auth.log | tail -10"

# Sitzungen mit ungewöhnlicher Dauer identifizieren
ssh "admin@$SERVER" "w -h | awk '{print \$1, \$2, \$3, \$4}' | sort -k 4" | while read user from login idle; do
    # Idle-Zeit in Minuten konvertieren
    if [[ "$idle" == *"days"* ]]; then
        echo "WARNUNG: Benutzer $user von $from ist seit $idle inaktiv!"
    fi
done

15.4.9 Fortgeschrittene SSH-Automatisierungstechniken

15.4.9.1 Master-Verbindungen für mehrere SSH-Sitzungen

#!/bin/bash

SERVER="frequent-access-server"
SOCKET_DIR="/tmp/ssh_mux"
SOCKET_FILE="$SOCKET_DIR/$SERVER"

# Socket-Verzeichnis erstellen
mkdir -p "$SOCKET_DIR"
chmod 700 "$SOCKET_DIR"

# Master-Verbindung öffnen
echo "Öffne Master-Verbindung zu $SERVER..."
ssh -M -S "$SOCKET_FILE" -o ControlPersist=10m "$SERVER" "echo 'Master-Verbindung hergestellt'"

# Funktion zum Ausführen von Befehlen über die Master-Verbindung
run_ssh_command() {
    local cmd="$1"
    echo "Führe aus: $cmd"
    ssh -S "$SOCKET_FILE" "$SERVER" "$cmd"
}

# Mehrere Befehle ausführen (sehr schnell, da eine Verbindung wiederverwendet wird)
run_ssh_command "uptime"
run_ssh_command "df -h"
run_ssh_command "free -m"

# Master-Verbindung schließen
echo "Schließe Master-Verbindung..."
ssh -S "$SOCKET_FILE" -O exit "$SERVER"

15.4.9.2 Jumphost-Konfiguration für den Zugriff auf interne Netzwerke

#!/bin/bash

# Direkter Zugriff über einen Jumphost
ssh -J jumphost.example.com internal-server.local "uptime"

# Alternative mit SSH-Konfiguration in ~/.ssh/config:
# Host internal-server
#     HostName internal-server.local
#     User admin
#     ProxyJump jumphost.example.com

# Mehrere Jumphosts verwenden
ssh -J jumphost1.example.com,jumphost2.example.com internal-server.local

# Dateiübertragung über einen Jumphost
scp -J jumphost.example.com local_file.txt internal-server.local:/destination/path/

15.4.9.3 Automatisches Deployment-Skript

#!/bin/bash

# Konfiguration
APP_NAME="myapp"
APP_VERSION="1.2.3"
SERVERS=("web1" "web2" "web3")
DEPLOY_USER="deployer"
DEPLOY_PATH="/var/www/$APP_NAME"
PACKAGE_FILE="${APP_NAME}-${APP_VERSION}.tar.gz"
BACKUP_DIR="/var/backups/$APP_NAME"

# Fehlerbehandlung
set -e  # Skript bei Fehlern beenden
trap 'echo "Deployment fehlgeschlagen!"; exit 1' ERR

echo "=== Deployment von $APP_NAME v$APP_VERSION startet ==="

# Paket auf jeden Server kopieren
for server in "${SERVERS[@]}"; do
    echo "Deployment auf $server..."
    
    # SSH-Session für mehrere Befehle verwenden
    ssh "$DEPLOY_USER@$server" << EOF
    # Verzeichnis vorbereiten
    if [[ ! -d "$DEPLOY_PATH" ]]; then
        mkdir -p "$DEPLOY_PATH"
    fi
    
    # Backup erstellen
    if [[ -d "$DEPLOY_PATH/current" ]]; then
        echo "Erstelle Backup..."
        mkdir -p "$BACKUP_DIR"
        cp -a "$DEPLOY_PATH/current" "$BACKUP_DIR/$APP_NAME-$(date +%Y%m%d-%H%M%S)"
        
        # Ältere Backups bereinigen
        find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;
    fi
    
    # Verzeichnis für neue Version erstellen
    mkdir -p "$DEPLOY_PATH/releases/$APP_VERSION"
EOF
    
    # Paket übertragen
    echo "Übertrage Paket auf $server..."
    scp "$PACKAGE_FILE" "$DEPLOY_USER@$server:/tmp/"
    
    # Paket entpacken und installieren
    ssh "$DEPLOY_USER@$server" << EOF
    # Paket entpacken
    tar -xzf "/tmp/$PACKAGE_FILE" -C "$DEPLOY_PATH/releases/$APP_VERSION"
    rm "/tmp/$PACKAGE_FILE"
    
    # Symlink aktualisieren
    if [[ -L "$DEPLOY_PATH/current" ]]; then
        rm "$DEPLOY_PATH/current"
    fi
    ln -sf "$DEPLOY_PATH/releases/$APP_VERSION" "$DEPLOY_PATH/current"
    
    # Berechtigungen anpassen
    find "$DEPLOY_PATH/current" -type f -exec chmod 644 {} \;
    find "$DEPLOY_PATH/current" -type d -exec chmod 755 {} \;
    
    # Anwendungsspezifische Aktionen
    if [[ -f "$DEPLOY_PATH/current/scripts/post-deploy.sh" ]]; then
        echo "Führe Post-Deployment-Skript aus..."
        cd "$DEPLOY_PATH/current" && ./scripts/post-deploy.sh
    fi
    
    # Service neustarten
    echo "Starte Anwendungsdienst neu..."
    sudo systemctl restart "$APP_NAME"
    
    # Status überprüfen
    sleep 2
    if sudo systemctl is-active "$APP_NAME" > /dev/null; then
        echo "Dienst erfolgreich gestartet"
    else
        echo "FEHLER: Dienst konnte nicht gestartet werden!"
        exit 1
    fi
EOF
    
    echo "Deployment auf $server abgeschlossen"
done

echo "=== Deployment erfolgreich abgeschlossen ==="

15.4.10 Best Practices für SSH-Automatisierung

  1. Verwenden Sie dedizierten SSH-Schlüssel für Automatisierungsaufgaben, nicht Ihre persönlichen Schlüssel.

  2. Beschränken Sie Schlüsselfunktionalität durch Einschränkungen in der authorized_keys-Datei.

  3. Implementieren Sie Timeouts für SSH-Verbindungen, um hängende Skripte zu vermeiden.

  4. Prüfen Sie Rückgabecodes von SSH-Befehlen, um Fehler zu erkennen.

  5. Protokollieren Sie Aktivitäten für Audit-Zwecke und zur Fehlersuche.

  6. Verwenden Sie SSH-Konfigurationsdateien für wiederholte Verbindungen.

  7. Verwenden Sie ControlMaster/ControlPersist für mehrere Befehle auf demselben Host.

  8. Implementieren Sie Fehlerbehandlung und Wiederholungslogik für instabile Netzwerke.

  9. Beschränken Sie den Zugriff auf Skripte mit sensiblen Informationen oder Schlüsseln.

  10. Überwachen Sie SSH-Aktivitäten auf verdächtige Muster.

Die Kombination aus SSH und Bash-Skripten bildet eine leistungsstarke Plattform für die Automatisierung von Netzwerkoperationen, Systemadministration und Deployment-Prozessen. Durch die Beachtung der Sicherheitsrichtlinien und bewährten Praktiken können Sie zuverlässige und sichere Automatisierungslösungen implementieren, die über verschiedene Netzwerkumgebungen hinweg funktionieren.

15.5 SSH-Agent für effiziente Authentifizierung

Der SSH-Agent ist ein Hintergrundprogramm, das private SSH-Schlüssel sicher im Speicher hält und die Authentifizierung verwaltet. Er ermöglicht die Verwendung passwortgeschützter Schlüssel in Automatisierungsskripten, ohne dass das Passwort jedes Mal eingegeben werden muss, und erhöht so sowohl die Sicherheit als auch die Benutzerfreundlichkeit.

15.5.1 Grundkonzepte des SSH-Agents

# SSH-Agent starten
eval $(ssh-agent)

# Alternativ mit Lebensdauer begrenzen (in Sekunden)
eval $(ssh-agent -t 3600)  # Agent läuft für 1 Stunde

# Schlüssel zum Agenten hinzufügen
ssh-add ~/.ssh/id_ed25519

# Passwortgeschützten Schlüssel hinzufügen (interaktive Passwortabfrage)
ssh-add ~/.ssh/secure_key

# Schlüssel mit bestimmtem Pfad hinzufügen
ssh-add /path/to/specific_key

# Alle Standardschlüssel hinzufügen
ssh-add

15.5.2 Anzeigen und Verwalten von Schlüsseln im Agent

# Alle geladenen Schlüssel anzeigen (Fingerabdrücke)
ssh-add -l

# Detaillierte Ausgabe mit vollständigem Pfad
ssh-add -L

# Spezifischen Schlüssel entfernen
ssh-add -d ~/.ssh/specific_key

# Alle Schlüssel entfernen
ssh-add -D

# SSH-Agent beenden
ssh-agent -k

15.5.3 SSH-Agent in Automatisierungsskripten

#!/bin/bash

# Funktion zur Verwaltung des SSH-Agents
setup_ssh_agent() {
    # Prüfen, ob bereits ein Agent läuft
    if [[ -z "$SSH_AUTH_SOCK" ]]; then
        echo "Starte neuen SSH-Agent..."
        eval $(ssh-agent)
        echo "SSH-Agent gestartet mit PID $SSH_AGENT_PID"
        
        # PID für späteres Beenden speichern
        echo "$SSH_AGENT_PID" > /tmp/ssh_agent_pid
    else
        echo "Verwende existierenden SSH-Agent mit Socket $SSH_AUTH_SOCK"
    fi
    
    # Schlüssel hinzufügen, falls noch nicht geladen
    if ! ssh-add -l | grep -q "$(ssh-keygen -lf ~/.ssh/automation_key | awk '{print $2}')"; then
        echo "Füge Automatisierungsschlüssel hinzu..."
        ssh-add ~/.ssh/automation_key
    fi
}

# Funktion zum Beenden des Agenten
cleanup_ssh_agent() {
    if [[ -f /tmp/ssh_agent_pid ]]; then
        local agent_pid=$(cat /tmp/ssh_agent_pid)
        echo "Beende SSH-Agent (PID $agent_pid)..."
        kill "$agent_pid"
        rm /tmp/ssh_agent_pid
    fi
}

# Agent einrichten
setup_ssh_agent

# Automatisierungsaufgaben ausführen
echo "Führe SSH-Befehle mit Agent-Unterstützung aus..."
ssh server1 "uptime"
ssh server2 "df -h"

# Bei Skript-Ende Agent beenden
trap cleanup_ssh_agent EXIT

15.5.4 Persistenter SSH-Agent für langlebige Prozesse

#!/bin/bash

AGENT_FILE="$HOME/.ssh/agent_info"

# Funktion zum Starten oder Wiederverwenden eines Agenten
start_or_reuse_agent() {
    # Prüfen, ob eine Agent-Datei existiert
    if [[ -f "$AGENT_FILE" ]]; then
        echo "Versuche, bestehenden Agent zu laden..."
        source "$AGENT_FILE"
        
        # Prüfen, ob der Agent noch läuft
        if kill -0 "$SSH_AGENT_PID" 2>/dev/null; then
            echo "Bestehender SSH-Agent läuft mit PID $SSH_AGENT_PID"
            
            # Testen, ob der Agent funktioniert
            if ssh-add -l &>/dev/null; then
                echo "SSH-Agent ist funktionsfähig"
                return 0
            else
                echo "SSH-Agent nicht funktionsfähig, starte neu..."
            fi
        else
            echo "SSH-Agent mit PID $SSH_AGENT_PID nicht mehr aktiv"
        fi
    fi
    
    # Neuen Agenten starten
    echo "Starte neuen SSH-Agent..."
    ssh-agent > "$AGENT_FILE"
    chmod 600 "$AGENT_FILE"
    source "$AGENT_FILE"
    echo "Neuer SSH-Agent gestartet mit PID $SSH_AGENT_PID"
    
    # Schlüssel hinzufügen
    echo "Füge Schlüssel hinzu..."
    ssh-add ~/.ssh/automation_key
    
    return 0
}

# Verwendung
start_or_reuse_agent

# Automatisierungstasks ausführen
ssh server "uptime"

15.5.5 SSH-Agent-Forwarding für mehrstufige Verbindungen

Agent-Forwarding ermöglicht es, den lokalen SSH-Agenten über mehrere Hops hinweg zu verwenden:

# SSH-Konfiguration für Agent-Forwarding in ~/.ssh/config
Host jumphost
    HostName jumphost.example.com
    ForwardAgent yes

Host internal
    HostName internal.example.local
    ProxyJump jumphost
    ForwardAgent yes
#!/bin/bash

# Sicheres Skript für Agent-Forwarding
# HINWEIS: Agent-Forwarding sollte nur mit vertrauenswürdigen Servern verwendet werden

# Ensure agent is running
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519

# Mehrstufige Verbindung mit Agent-Forwarding
echo "Verbinde mit Zielserver über Jumphost..."
ssh -A jumphost ssh targetserver "git clone git@github.com:user/private-repo.git"

# Alternative mit ProxyJump
ssh -J jumphost -A targetserver "git clone git@github.com:user/private-repo.git"

# Agent beenden
ssh-agent -k

15.5.6 Sicherheitsüberlegungen zum SSH-Agent

Der SSH-Agent bietet Komfort, kann aber bei unsachgemäßer Verwendung Sicherheitsrisiken darstellen:

  1. Begrenzte Lebensdauer: Setzen Sie eine zeitliche Begrenzung für den Agenten mit -t (Sekunden).

  2. Eingeschränkte Schlüsselverwendung: Verwenden Sie ssh-add -c, um Bestätigungen für jede Schlüsselverwendung zu fordern.

  3. Vorsicht bei Agent-Forwarding: Verwenden Sie Agent-Forwarding nur mit vertrauenswürdigen Servern, da der Root-Benutzer des Zielservers auf Ihren Agenten zugreifen kann.

  4. Dedizierte Agenten: Verwenden Sie für Automatisierungsaufgaben einen separaten Agenten mit eingeschränkten Schlüsseln.

  5. Überwachung: Protokollieren Sie die Verwendung des Agenten für Audit-Zwecke.

#!/bin/bash

# Beispiel: Eingeschränkter SSH-Agent für Automatisierung

# Dedizierter Agent nur für die Dauer des Skripts
eval $(ssh-agent)

# Bestätigungsaufforderung für Schlüsselverwendung aktivieren
# (Beachten Sie: Dies funktioniert nur bei interaktiven Sitzungen)
ssh-add -c ~/.ssh/automation_key

# Alternativ: Zeitlich begrenzter Agent
eval $(ssh-agent -t 1800)  # 30 Minuten
ssh-add ~/.ssh/automation_key

# Ausführung der automatisierten Aufgaben
ssh server1 "backup_script"

# Agent beenden
ssh-agent -k

Der SSH-Agent ist ein unverzichtbares Werkzeug für die SSH-Automatisierung, insbesondere wenn passwortgeschützte Schlüssel verwendet werden oder wenn komplexe mehrstufige Verbindungen erforderlich sind. Durch die richtige Konfiguration und Verwendung des Agenten lässt sich ein gutes Gleichgewicht zwischen Sicherheit und Benutzerfreundlichkeit erzielen.

15.6 Einfache Netzwerkdienste mit netcat

Netcat (oft abgekürzt als nc) ist ein vielseitiges Netzwerkdienstprogramm, das als “Schweizer Taschenmesser” für TCP/IP-Verbindungen gilt. Es kann als einfacher Server oder Client fungieren, Daten über Netzwerkverbindungen übertragen und empfangen und ist ein leistungsstarkes Werkzeug für Netzwerktests, Debugging und sogar einfache Client-Server-Anwendungen. In diesem Abschnitt werden wir die grundlegenden Funktionen von netcat und deren Integration in Bash-Skripte untersuchen.

15.6.1 Grundlegende Verwendung von netcat

Netcat ist in fast allen Linux-Distributionen verfügbar und kann in zwei Hauptmodi betrieben werden: als Client oder als Server.

15.6.1.1 Als Client

# Verbindung zu einem Server herstellen
nc server.example.com 80

# Verbindung mit Timeout (in Sekunden)
nc -w 5 server.example.com 80

# Verbindung zu UDP-Port
nc -u server.example.com 53

15.6.1.2 Als Server

# Auf TCP-Port 12345 lauschen
nc -l 12345

# Auf spezifischer IP-Adresse und Port lauschen
nc -l 192.168.1.10 12345

# Auf UDP-Port lauschen
nc -u -l 12345

15.6.2 Datenübertragung mit netcat

Netcat kann einfach für die Übertragung von Daten zwischen Systemen verwendet werden:

15.6.2.1 Dateiübertragung

# Auf Empfängerseite (Server)
nc -l 12345 > empfangene_datei.txt

# Auf Senderseite (Client)
nc server.example.com 12345 < zu_sendende_datei.txt

15.6.2.2 Verzeichnisübertragung

# Auf Empfängerseite
nc -l 12345 | tar xzf -

# Auf Senderseite
tar czf - verzeichnis/ | nc server.example.com 12345

15.6.3 Einfache Chat-Anwendung

Netcat kann für einfache bidirektionale Kommunikation genutzt werden:

# Server-Seite
nc -l 12345

# Client-Seite
nc server.example.com 12345

Nachdem die Verbindung hergestellt ist, kann jede Seite Nachrichten eingeben, die auf der anderen Seite angezeigt werden.

15.6.4 Netzwerk-Debugging mit netcat

Netcat ist ein wertvolles Werkzeug für das Debugging von Netzwerkdiensten:

# HTTP-Anfrage manuell senden
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" | nc example.com 80

# Port-Scan mit Netcat
for port in {20..25}; do
    nc -zv server.example.com $port 2>&1 | grep succeeded
done

# Banner-Grabbing (Diensterkennung)
echo "" | nc -v -n -w 1 192.168.1.1 22

15.6.5 Persistente Verbindungen

Netcat kann in einigen Varianten mit der Option -k verwendet werden, um persistente Verbindungen aufrechtzuerhalten:

# Persistenter Server mit GNU Netcat
nc -k -l 12345

# Alternativ mit traditionellem Netcat über eine Schleife
while true; do
    echo "Server gestartet auf Port 12345..."
    nc -l 12345
    echo "Verbindung geschlossen, starte neu..."
    sleep 1
done

15.6.6 Einfache Webserver mit netcat

Ein minimalistischer HTTP-Server kann mit netcat implementiert werden:

#!/bin/bash

PORT=8080
RESPONSE="HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<html><body><h1>Hallo von Netcat-Webserver</h1></body></html>\r\n"

while true; do
    echo -e "$RESPONSE" | nc -l -p $PORT
    echo "Anfrage beantwortet"
done

Für einen etwas robusteren Server mit Datei-Serving:

#!/bin/bash

PORT=8080
WEBROOT="/var/www/html"

while true; do
    echo "Warte auf Verbindungen auf Port $PORT..."
    REQUEST=$(nc -l $PORT)
    
    # HTTP-Anfrage parsen
    RESOURCE=$(echo "$REQUEST" | head -n 1 | awk '{print $2}')
    
    echo "Anfrage für Ressource: $RESOURCE"
    
    # Standardseite, wenn / angefordert wird
    if [[ "$RESOURCE" == "/" ]]; then
        RESOURCE="/index.html"
    fi
    
    # Vollständiger Pfad zur angeforderten Datei
    FILEPATH="${WEBROOT}${RESOURCE}"
    
    # Prüfen, ob Datei existiert und lesbar ist
    if [[ -f "$FILEPATH" && -r "$FILEPATH" ]]; then
        # MIME-Typ bestimmen
        CONTENT_TYPE=$(file --mime-type -b "$FILEPATH")
        
        # HTTP-Antwort senden
        {
            echo -e "HTTP/1.1 200 OK\r"
            echo -e "Content-Type: $CONTENT_TYPE\r"
            echo -e "\r"
            cat "$FILEPATH"
        } | nc -l $PORT
    else
        # 404 Not Found
        {
            echo -e "HTTP/1.1 404 Not Found\r"
            echo -e "Content-Type: text/html\r"
            echo -e "\r"
            echo -e "<html><body><h1>404 Not Found</h1><p>Die angeforderte Ressource $RESOURCE wurde nicht gefunden.</p></body></html>\r"
        } | nc -l $PORT
    fi
    
    echo "Anfrage beantwortet"
    sleep 1
done

15.6.7 Proxy-Server mit netcat

Ein einfacher TCP-Proxy kann mit netcat implementiert werden:

#!/bin/bash

LOCAL_PORT=8080
REMOTE_HOST="example.com"
REMOTE_PORT=80

echo "Starte Proxy auf Port $LOCAL_PORT zu $REMOTE_HOST:$REMOTE_PORT"

# Option 1: Mit mkfifo (effizient)
FIFO="/tmp/proxy_fifo"
mkfifo "$FIFO"

while true; do
    nc -l $LOCAL_PORT < "$FIFO" | nc $REMOTE_HOST $REMOTE_PORT > "$FIFO"
done

rm -f "$FIFO"

# Option 2: Alternative mit separaten Prozessen
# while true; do
#     nc -l $LOCAL_PORT | tee /tmp/proxy_out | nc $REMOTE_HOST $REMOTE_PORT | tee /tmp/proxy_in | nc -l $LOCAL_PORT
# done

15.6.8 Einfache Netzwerküberwachung

Netcat kann für einfache Netzwerküberwachung verwendet werden:

#!/bin/bash

# Einfache Dienstverfügbarkeitsüberwachung
HOSTS=("web.example.com:80" "db.example.com:3306" "mail.example.com:25")
LOG_FILE="/var/log/service_monitor.log"

check_service() {
    local host_port="$1"
    local host=$(echo "$host_port" | cut -d: -f1)
    local port=$(echo "$host_port" | cut -d: -f2)
    
    # Timeout von 3 Sekunden
    if nc -z -w 3 "$host" "$port" 2>/dev/null; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $host:$port ist ERREICHBAR" >> "$LOG_FILE"
        return 0
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $host:$port ist NICHT ERREICHBAR" >> "$LOG_FILE"
        return 1
    fi
}

# Hauptfunktion
echo "=== Dienstverfügbarkeitsüberwachung gestartet $(date) ===" >> "$LOG_FILE"

for host_port in "${HOSTS[@]}"; do
    if ! check_service "$host_port"; then
        # Hier könnte eine Benachrichtigung erfolgen
        echo "WARNUNG: $host_port ist nicht erreichbar!" >&2
    fi
done

15.6.9 Sicherheitsaspekte bei der Verwendung von netcat

#!/bin/bash

# Sicherer Netcat-Server mit Autologout nach 60 Sekunden Inaktivität
# und Begrenzung der max. Verbindungszeit auf 5 Minuten

PORT=12345
MAX_TIME=300  # 5 Minuten in Sekunden
TIMEOUT=60    # 60 Sekunden Inaktivitätstimeout

echo "Starte sicheren Netcat-Server auf Port $PORT..."

# PID des Netcat-Prozesses speichern
nc -l $PORT &
NC_PID=$!

# Timeout für Inaktivität
( sleep $TIMEOUT && kill -0 $NC_PID 2>/dev/null && { echo "Timeout wegen Inaktivität"; kill $NC_PID; } ) &
TIMEOUT_PID=$!

# Maximale Verbindungszeit
( sleep $MAX_TIME && kill -0 $NC_PID 2>/dev/null && { echo "Maximale Verbindungszeit erreicht"; kill $NC_PID; } ) &
MAXTIME_PID=$!

# Warten auf Beendigung des Netcat-Prozesses
wait $NC_PID

# Timeout-Prozesse beenden
kill $TIMEOUT_PID 2>/dev/null
kill $MAXTIME_PID 2>/dev/null

echo "Netcat-Server beendet"

15.6.10 Netcat für Remote-Ausführung (nur zu Bildungszwecken)

Hinweis: Die folgenden Beispiele dienen ausschließlich Bildungszwecken und sollten nur in kontrollierten Umgebungen mit angemessenen Sicherheitsmaßnahmen verwendet werden.

# Auf der Server-Seite (empfangender Host):
nc -l 12345 -e /bin/bash

# Alternativ, wenn -e nicht verfügbar ist:
mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc -l 12345 > /tmp/f; rm /tmp/f

# Auf der Client-Seite:
nc server.example.com 12345

15.6.11 Integration von netcat in komplexere Skripte

15.6.11.1 Backup-Script mit Fortschrittsüberwachung

#!/bin/bash

# Backup-Skript mit Netcat-basierter Fortschrittsüberwachung

SOURCE_DIR="/home/user/data"
REMOTE_HOST="backup.example.com"
REMOTE_PORT=12345
BACKUP_NAME="backup-$(date +%Y%m%d).tar.gz"

# Größe der zu sichernden Daten ermitteln
TOTAL_SIZE=$(du -sb "$SOURCE_DIR" | awk '{print $1}')

# Status-Server starten
status_server() {
    local port=$1
    
    # Einfacher HTTP-Server, der den Fortschritt zurückgibt
    while true; do
        echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nBackup-Fortschritt: $PROGRESS von $TOTAL_SIZE Bytes ($PERCENT%)\r\n" | nc -l $port
    done
}

# Fortschrittsvariablen
PROGRESS=0
PERCENT=0

# Status-Server im Hintergrund starten
status_server 8080 &
STATUS_PID=$!

# Pipe für tar-Ausgabe
mkfifo /tmp/backup_pipe
exec 3<> /tmp/backup_pipe

# Fortschritt überwachen
(
    while read -r line; do
        # Fortschritt aktualisieren (vereinfacht)
        PROGRESS=$((PROGRESS + 1024))
        PERCENT=$((PROGRESS * 100 / TOTAL_SIZE))
        if [[ $((PROGRESS % 10485760)) -eq 0 ]]; then  # Alle 10MB aktualisieren
            echo "Fortschritt: $PERCENT% ($PROGRESS von $TOTAL_SIZE Bytes)"
        fi
    done < /tmp/backup_pipe
) &
MONITOR_PID=$!

# Backup durchführen
echo "Starte Backup von $SOURCE_DIR nach $REMOTE_HOST:$REMOTE_PORT..."
tar czf - -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")" 2>/dev/null | tee /tmp/backup_pipe | nc $REMOTE_HOST $REMOTE_PORT

# Aufräumen
kill $STATUS_PID 2>/dev/null
kill $MONITOR_PID 2>/dev/null
exec 3>&-
rm -f /tmp/backup_pipe

echo "Backup abgeschlossen."

15.6.11.2 Verteilte Protokollsammlung

#!/bin/bash

# Zenralisierte Protokollsammlung von mehreren Servern

# Server-Modus: Protokolle sammeln
log_collector() {
    local port=12345
    local log_dir="/var/log/remote"
    
    mkdir -p "$log_dir"
    
    echo "Starte Protokollsammler auf Port $port..."
    
    while true; do
        # Format: HOSTNAME:LOGFILE:DATA
        nc -l $port | while IFS=':' read -r hostname logfile data; do
            # Zielverzeichnis für diesen Host
            local host_dir="$log_dir/$hostname"
            mkdir -p "$host_dir"
            
            # Zum Log hinzufügen
            echo "$(date '+%Y-%m-%d %H:%M:%S') $data" >> "$host_dir/$(basename "$logfile")"
            echo "Protokolleintrag von $hostname für $(basename "$logfile") empfangen"
        done
    done
}

# Client-Modus: Protokolle senden
log_sender() {
    local collector_host="$1"
    local collector_port=12345
    local log_file="$2"
    
    hostname=$(hostname)
    
    # Datei überwachen und neue Zeilen senden
    tail -f "$log_file" | while read -r line; do
        echo "$hostname:$log_file:$line" | nc -w 2 $collector_host $collector_port
    done
}

# Verwendung basierend auf Parametern
case "$1" in
    "collector")
        log_collector
        ;;
    "sender")
        if [[ -z "$2" || -z "$3" ]]; then
            echo "Verwendung: $0 sender <collector_host> <log_file>"
            exit 1
        fi
        log_sender "$2" "$3"
        ;;
    *)
        echo "Verwendung: $0 {collector|sender <collector_host> <log_file>}"
        exit 1
        ;;
esac

15.6.12 Alternativen zu netcat

Obwohl netcat ein vielseitiges Werkzeug ist, gibt es einige Situationen, in denen Alternativen vorzuziehen sind:

  1. socat: Ein leistungsfähigerer Nachfolger von netcat mit erweiterten Funktionen:
# Einfacher TCP-Server mit socat
socat TCP-LISTEN:12345,fork STDOUT

# Bidirektionaler Datentransfer
socat TCP-LISTEN:12345,fork TCP:target.example.com:80

# Unix-Domain-Socket-Server
socat UNIX-LISTEN:/tmp/socket,fork STDOUT
  1. ncat (aus dem Nmap-Projekt): Eine verbesserte Version von netcat mit zusätzlichen Sicherheitsfunktionen:
# SSL/TLS-verschlüsselter Server
ncat --ssl -l 12345

# Verbindung mit Client-Zertifikatsauthentifizierung
ncat --ssl --ssl-verify --ssl-cert cert.pem --ssl-key key.pem -l 12345

# Verbindungsbeschränkung auf bestimmte IPs
ncat -l 12345 --allow 192.168.1.0/24

15.6.13 Best Practices für netcat in Produktionsumgebungen

  1. Sicherheitsüberlegungen: Begrenzen Sie die Exposition von netcat-Servern auf vertrauenswürdige Netzwerke.

  2. Timeouts implementieren: Verwenden Sie immer angemessene Timeouts, um hängende Verbindungen zu vermeiden.

  3. Verbindungsbegrenzung: Begrenzen Sie die Anzahl gleichzeitiger Verbindungen, um Ressourcenerschöpfung zu verhindern.

  4. Protokollierung: Implementieren Sie Protokollierung für alle netcat-Operationen zur Fehlersuche und Prüfung.

  5. Berechtigungen: Führen Sie netcat mit den geringsten erforderlichen Berechtigungen aus.

  6. Alternative Tools: Erwägen Sie für Produktionsumgebungen spezialisiertere Tools wie socat oder ncat.

  7. Escape-Zeichen beachten: Achten Sie auf korrekte Handhabung von Zeilenumbrüchen und Escape-Sequenzen.

Netcat ist ein leistungsstarkes und vielseitiges Werkzeug für Netzwerkoperationen und kann in Bash-Skripten effektiv für verschiedene Aufgaben eingesetzt werden, von einfachen Netzwerktests bis hin zu komplexeren Client-Server-Interaktionen. Mit angemessener Sorgfalt hinsichtlich Sicherheit und Fehlerbehandlung kann netcat ein wertvoller Bestandteil des Werkzeugkastens eines Systemadministrators oder Entwicklers sein.

15.7 Paketfilterung und Firewall-Konfiguration

Die Netzwerksicherheit ist ein wesentlicher Bestandteil jeder Systemadministration. In Linux-Umgebungen wird die Paketfilterung hauptsächlich durch den Kernel-Firewall-Mechanismus netfilter implementiert, der über die Kommandozeilen-Tools iptables (für IPv4) und ip6tables (für IPv6) konfiguriert wird. In modernen Linux-Distributionen steht zusätzlich nftables als Nachfolger zur Verfügung. In diesem Abschnitt werden wir erkunden, wie Bash-Skripte zur automatisierten Konfiguration, Überwachung und Verwaltung von Firewalls eingesetzt werden können.

15.7.1 Grundlagen von iptables

iptables organisiert Filterregeln in Tabellen und Ketten. Die wichtigsten Tabellen sind:

Jede Tabelle enthält vordefinierte Ketten:

15.7.2 Grundlegende iptables-Befehle

# Regeln anzeigen
sudo iptables -L -v

# Regel hinzufügen (SSH erlauben)
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Regel mit Quell-IP-Beschränkung
sudo iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# Regel löschen
sudo iptables -D INPUT -p tcp --dport 22 -j ACCEPT

# Regel am Anfang einer Kette einfügen
sudo iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT

# Standardrichtlinie für eine Kette setzen
sudo iptables -P INPUT DROP

15.7.3 Einfaches Firewall-Skript

#!/bin/bash

# Einfaches Firewall-Skript für einen Server

# Sicherstellen, dass das Skript als Root ausgeführt wird
if [[ $EUID -ne 0 ]]; then
   echo "Dieses Skript muss als Root ausgeführt werden" >&2
   exit 1
fi

# Bestehende Regeln löschen
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

# Standardrichtlinien setzen
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Loopback-Interface erlauben
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Bestehende Verbindungen erlauben
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# SSH-Zugriff erlauben
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# HTTP und HTTPS erlauben
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# ICMP (Ping) erlauben
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# DNS-Antworten erlauben
iptables -A INPUT -p udp --sport 53 -j ACCEPT

# Alle anderen eingehenden Pakete protokollieren und verwerfen
iptables -A INPUT -j LOG --log-prefix "IPTables-Dropped: " --log-level 4
iptables -A INPUT -j DROP

echo "Firewall-Regeln wurden konfiguriert."

15.7.4 Erweitertes Firewall-Skript mit Parametrisierung

#!/bin/bash

# Erweitertes Firewall-Skript mit Konfigurationsdatei

CONFIG_FILE="/etc/firewall/config"
BACKUP_DIR="/var/backups/firewall"
LOG_FILE="/var/log/firewall.log"

# Standardwerte
SSH_PORT=22
HTTP_ENABLED=1
HTTPS_ENABLED=1
ALLOWED_IPS=()
CUSTOM_RULES_FILE=""
RATE_LIMIT_SSH=1  # 1 für aktiviert, 0 für deaktiviert
MAX_SSH_CONNECTIONS=5
SSH_RATE_TIME=60  # Sekunden

# Logging-Funktion
log() {
    local level="$1"
    local message="$2"
    echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $message" >> "$LOG_FILE"
    echo "[$level] $message"
}

# Konfigurationsdatei laden, falls vorhanden
if [[ -f "$CONFIG_FILE" ]]; then
    log "INFO" "Lade Konfiguration aus $CONFIG_FILE"
    source "$CONFIG_FILE"
else
    log "WARN" "Konfigurationsdatei $CONFIG_FILE nicht gefunden, verwende Standardwerte"
fi

# Verzeichnisse erstellen, falls sie nicht existieren
mkdir -p "$(dirname "$LOG_FILE")" "$BACKUP_DIR"

# Aktuelle Regeln sichern
iptables-save > "$BACKUP_DIR/iptables-$(date +%Y%m%d-%H%M%S).rules"

# Bestehende Regeln löschen
reset_firewall() {
    log "INFO" "Lösche bestehende Firewall-Regeln"
    iptables -F
    iptables -X
    iptables -t nat -F
    iptables -t nat -X
    iptables -t mangle -F
    iptables -t mangle -X
    
    # Standardrichtlinien setzen
    iptables -P INPUT DROP
    iptables -P FORWARD DROP
    iptables -P OUTPUT ACCEPT
}

# Grundlegende Regeln anwenden
apply_basic_rules() {
    log "INFO" "Konfiguriere grundlegende Firewall-Regeln"
    
    # Loopback-Interface erlauben
    iptables -A INPUT -i lo -j ACCEPT
    iptables -A OUTPUT -o lo -j ACCEPT
    
    # Bestehende Verbindungen erlauben
    iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    
    # ICMP (Ping) erlauben - mit Rate-Limiting
    iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 4 -j ACCEPT
    
    # DNS-Antworten erlauben
    iptables -A INPUT -p udp --sport 53 -j ACCEPT
}

# Dienste konfigurieren
configure_services() {
    # SSH
    log "INFO" "Konfiguriere SSH-Zugriff auf Port $SSH_PORT"
    
    # SSH mit Rate-Limiting für Brute-Force-Schutz
    if [[ "$RATE_LIMIT_SSH" -eq 1 ]]; then
        log "INFO" "Aktiviere Rate-Limiting für SSH"
        # Neue Kette für SSH-Verbindungen
        iptables -N SSH_CHECK
        
        # Begrenze neue Verbindungen
        iptables -A SSH_CHECK -m conntrack --ctstate NEW -m recent --set
        iptables -A SSH_CHECK -m conntrack --ctstate NEW -m recent --update --seconds $SSH_RATE_TIME --hitcount $MAX_SSH_CONNECTIONS -j DROP
        
        # SSH-Verkehr an die SSH_CHECK-Kette weiterleiten
        iptables -A INPUT -p tcp --dport $SSH_PORT -j SSH_CHECK
    fi
    
    # SSH-Zugriff erlauben
    if [[ ${#ALLOWED_IPS[@]} -gt 0 ]]; then
        # Nur von bestimmten IPs erlauben
        for ip in "${ALLOWED_IPS[@]}"; do
            iptables -A INPUT -p tcp --dport $SSH_PORT -s "$ip" -j ACCEPT
        done
        log "INFO" "SSH-Zugriff auf die IPs beschränkt: ${ALLOWED_IPS[*]}"
    else
        # Von überall erlauben
        iptables -A INPUT -p tcp --dport $SSH_PORT -j ACCEPT
    fi
    
    # HTTP und HTTPS
    [[ "$HTTP_ENABLED" -eq 1 ]] && {
        log "INFO" "Erlaube HTTP-Verkehr"
        iptables -A INPUT -p tcp --dport 80 -j ACCEPT
    }
    
    [[ "$HTTPS_ENABLED" -eq 1 ]] && {
        log "INFO" "Erlaube HTTPS-Verkehr"
        iptables -A INPUT -p tcp --dport 443 -j ACCEPT
    }
}

# Benutzerdefinierte Regeln laden
load_custom_rules() {
    if [[ -n "$CUSTOM_RULES_FILE" && -f "$CUSTOM_RULES_FILE" ]]; then
        log "INFO" "Lade benutzerdefinierte Regeln aus $CUSTOM_RULES_FILE"
        source "$CUSTOM_RULES_FILE"
    fi
}

# Abschlussregeln
finalize_rules() {
    log "INFO" "Konfiguriere Abschlussregeln"
    
    # Protokollierung für verworfene Pakete
    iptables -A INPUT -j LOG --log-prefix "IPTables-Dropped: " --log-level 4
    iptables -A INPUT -j DROP
    
    log "INFO" "Firewall-Konfiguration abgeschlossen"
}

# Hauptfunktion
configure_firewall() {
    log "INFO" "Starte Firewall-Konfiguration"
    
    reset_firewall
    apply_basic_rules
    configure_services
    load_custom_rules
    finalize_rules
    
    # Regeln speichern
    if command -v iptables-save >/dev/null 2>&1; then
        iptables-save > /etc/iptables/rules.v4
        log "INFO" "Firewall-Regeln wurden in /etc/iptables/rules.v4 gespeichert"
    fi
}

# Firewall-Status anzeigen
show_status() {
    echo "Aktuelle Firewall-Regeln:"
    echo "------------------------"
    iptables -L -v
}

# Skriptausführung basierend auf Parametern
case "$1" in
    start|restart)
        configure_firewall
        ;;
    stop)
        log "INFO" "Deaktiviere Firewall"
        reset_firewall
        # Alles erlauben
        iptables -P INPUT ACCEPT
        iptables -P FORWARD ACCEPT
        iptables -P OUTPUT ACCEPT
        ;;
    status)
        show_status
        ;;
    *)
        echo "Verwendung: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

exit 0

15.7.5 Beispiel einer Konfigurationsdatei

# /etc/firewall/config

# SSH-Konfiguration
SSH_PORT=2222
RATE_LIMIT_SSH=1
MAX_SSH_CONNECTIONS=3
SSH_RATE_TIME=60

# Webdienste
HTTP_ENABLED=1
HTTPS_ENABLED=1

# Zugriffsbeschränkungen
ALLOWED_IPS=("192.168.1.0/24" "10.0.0.5")

# Benutzerdefinierte Regeln
CUSTOM_RULES_FILE="/etc/firewall/custom_rules.sh"

15.7.6 Benutzerdefinierte Regeln

# /etc/firewall/custom_rules.sh

# FTP passiv erlauben
iptables -A INPUT -p tcp --dport 21 -j ACCEPT
# Passive FTP-Ports
iptables -A INPUT -p tcp --dport 1024:1048 -j ACCEPT

# MySQL-Zugriff nur vom lokalen Netzwerk
iptables -A INPUT -p tcp --dport 3306 -s 192.168.1.0/24 -j ACCEPT

# OpenVPN
iptables -A INPUT -p udp --dport 1194 -j ACCEPT

15.7.7 nftables - Der Nachfolger von iptables

nftables ist der designierte Nachfolger von iptables und bietet eine verbesserte Syntax und Leistung:

#!/bin/bash

# Grundlegendes nftables-Firewall-Skript

# Sicherstellen, dass das Skript als Root ausgeführt wird
if [[ $EUID -ne 0 ]]; then
   echo "Dieses Skript muss als Root ausgeführt werden" >&2
   exit 1
fi

# Temporäre Datei für die Regeln
TEMP_FILE=$(mktemp)

# nftables-Regeln in die temporäre Datei schreiben
cat > "$TEMP_FILE" << 'EOF'
#!/usr/sbin/nft -f

# Bestehende Regeln löschen
flush ruleset

# Tabelle für IPv4-Filter definieren
table inet filter {
    # Basis-Ketten definieren
    chain input {
        type filter hook input priority 0; policy drop;
        
        # Loopback-Verkehr erlauben
        iif lo accept
        
        # Bestehende Verbindungen erlauben
        ct state established,related accept
        
        # ICMP und IGMP erlauben
        ip protocol icmp accept
        ip6 nexthdr ipv6-icmp accept
        
        # SSH erlauben
        tcp dport 22 accept
        
        # HTTP und HTTPS erlauben
        tcp dport { 80, 443 } accept
        
        # DNS-Antworten erlauben
        udp sport 53 accept
        
        # Alles andere protokollieren und verwerfen
        log prefix "nftables-dropped: " drop
    }
    
    chain forward {
        type filter hook forward priority 0; policy drop;
    }
    
    chain output {
        type filter hook output priority 0; policy accept;
    }
}
EOF

# Regeln anwenden
nft -f "$TEMP_FILE"

# Backup erstellen
cp "$TEMP_FILE" /etc/nftables.conf

# Temporäre Datei löschen
rm "$TEMP_FILE"

echo "nftables-Firewall wurde konfiguriert und in /etc/nftables.conf gespeichert."

15.7.8 Firewall-Überwachung und -Verwaltung

15.7.8.1 Protokollierung und Analyse

#!/bin/bash

# Firewall-Protokollanalyse-Skript

LOG_FILE="/var/log/kern.log"
FIREWALL_LOG="/var/log/firewall-analysis.log"
TEMP_FILE=$(mktemp)

# Firewall-Einträge extrahieren (für iptables)
grep "IPTables-Dropped" "$LOG_FILE" > "$TEMP_FILE"

# Statistiken erstellen
echo "=== Firewall-Protokollanalyse $(date) ===" > "$FIREWALL_LOG"
echo "" >> "$FIREWALL_LOG"

echo "Die 10 häufigsten Quell-IPs:" >> "$FIREWALL_LOG"
grep -oE "SRC=[0-9.]+" "$TEMP_FILE" | sort | uniq -c | sort -nr | head -10 >> "$FIREWALL_LOG"

echo "" >> "$FIREWALL_LOG"
echo "Die 10 häufigsten Zielports:" >> "$FIREWALL_LOG"
grep -oE "DPT=[0-9]+" "$TEMP_FILE" | sort | uniq -c | sort -nr | head -10 >> "$FIREWALL_LOG"

echo "" >> "$FIREWALL_LOG"
echo "Protokollverteilung:" >> "$FIREWALL_LOG"
grep -oE "PROTO=[A-Za-z0-9]+" "$TEMP_FILE" | sort | uniq -c | sort -nr >> "$FIREWALL_LOG"

echo "" >> "$FIREWALL_LOG"
echo "Zeitliche Verteilung (pro Stunde):" >> "$FIREWALL_LOG"
grep -oE "^[A-Za-z]{3}\ [0-9]+\ [0-9]{2}" "$TEMP_FILE" | sort | uniq -c >> "$FIREWALL_LOG"

# Für nftables
grep "nftables-dropped" "$LOG_FILE" >> "$TEMP_FILE"
# Entsprechende Analyseschritte für nftables-Format hinzufügen...

# Temporäre Datei löschen
rm "$TEMP_FILE"

echo "Firewall-Protokollanalyse wurde in $FIREWALL_LOG gespeichert."

15.7.8.2 Dynamische Regeln für Angriffsabwehr

#!/bin/bash

# Automatisches Blockieren von potenziellen Angreifern

LOG_FILE="/var/log/kern.log"
BLOCK_TIME=3600  # Blockierungsdauer in Sekunden
MAX_ATTEMPTS=5   # Maximale Anzahl von Verbindungsversuchen
WHITELIST=("192.168.1.0/24" "10.0.0.5")  # Nicht zu blockierende IPs

# Temporäre Datei für die IP-Liste
TEMP_FILE=$(mktemp)

# SSH-Brute-Force-Versuche erkennen
grep "Failed password" /var/log/auth.log | grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | sort | uniq -c > "$TEMP_FILE"

# IP-Tabelle für blockierte Adressen erstellen (falls noch nicht vorhanden)
iptables -L BLOCKED 2>/dev/null || iptables -N BLOCKED

# Alte Regeln löschen (nach Ablauf der Blockierungszeit)
find /tmp/blocked_ips.* -type f -mmin +$((BLOCK_TIME / 60)) -exec rm {} \;

# INPUT-Kette aktualisieren, um BLOCKED zu verwenden (falls noch nicht vorhanden)
iptables -C INPUT -j BLOCKED 2>/dev/null || iptables -A INPUT -j BLOCKED

# Neue IPs blockieren
while read count ip; do
    # Prüfen, ob die Anzahl der Versuche den Schwellenwert überschreitet
    if [[ $count -ge $MAX_ATTEMPTS ]]; then
        # Prüfen, ob die IP nicht auf der Whitelist steht
        is_whitelisted=0
        for white_ip in "${WHITELIST[@]}"; do
            if [[ "$ip" == "$white_ip" || "$(ipcalc -n "$ip" "$white_ip" | grep -c 'NETWORK')" -eq 1 ]]; then
                is_whitelisted=1
                break
            fi
        done
        
        # IP blockieren, wenn nicht auf der Whitelist
        if [[ $is_whitelisted -eq 0 ]]; then
            # Prüfen, ob die IP bereits blockiert ist
            if ! iptables -C BLOCKED -s "$ip" -j DROP 2>/dev/null; then
                echo "Blockiere IP $ip für $BLOCK_TIME Sekunden (Grund: $count fehlgeschlagene SSH-Anmeldeversuche)"
                iptables -A BLOCKED -s "$ip" -j DROP
                
                # Zeitstempel für späteres Entfernen speichern
                touch "/tmp/blocked_ips.$ip"
            fi
        fi
    fi
done < "$TEMP_FILE"

# Temporäre Datei löschen
rm "$TEMP_FILE"

# Alte Blockierungen entfernen
for block_file in /tmp/blocked_ips.*; do
    [[ -f "$block_file" ]] || continue
    
    # IP aus dem Dateinamen extrahieren
    ip=$(basename "$block_file" | cut -d. -f2-)
    
    # Prüfen, ob die Blockierungszeit abgelaufen ist
    if [[ -f "$block_file" && $(find "$block_file" -mmin +$((BLOCK_TIME / 60)) | wc -l) -gt 0 ]]; then
        echo "Entferne Blockierung für IP $ip (Zeitraum abgelaufen)"
        iptables -D BLOCKED -s "$ip" -j DROP 2>/dev/null
        rm "$block_file"
    fi
done

15.7.9 Port-Knocking-Implementierung

Port-Knocking ist eine Technik, bei der bestimmte Ports in einer definierten Reihenfolge angeklopft werden müssen, bevor ein normalerweise geschlossener Port (z.B. SSH) geöffnet wird:

#!/bin/bash

# Port-Knocking-Implementierung

# Konfiguration
KNOCK_SEQUENCE=(7000 8000 9000)  # Ports, die in dieser Reihenfolge angeklopft werden müssen
OPEN_PORT=22                     # Port, der nach erfolgreichem Klopfen geöffnet wird
ALLOW_TIME=30                    # Zeit in Sekunden, für die der Port geöffnet bleibt
LOG_FILE="/var/log/knockd.log"

# Sicherstellen, dass das Skript als Root ausgeführt wird
if [[ $EUID -ne 0 ]]; then
   echo "Dieses Skript muss als Root ausgeführt werden" >&2
   exit 1
fi

# Protokollierungsfunktion
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

# Iptables-Ketten einrichten
setup_chains() {
    # KNOCKSEQUENCE-Kette erstellen, falls noch nicht vorhanden
    iptables -L KNOCKSEQUENCE &>/dev/null || iptables -N KNOCKSEQUENCE
    
    # KNOCKAUTH-Kette erstellen, falls noch nicht vorhanden
    iptables -L KNOCKAUTH &>/dev/null || iptables -N KNOCKAUTH
    
    # Sicherstellen, dass der Zielport standardmäßig blockiert ist
    iptables -C INPUT -p tcp --dport $OPEN_PORT -j KNOCKAUTH 2>/dev/null || \
        iptables -A INPUT -p tcp --dport $OPEN_PORT -j KNOCKAUTH
    
    # Klopfsequenz an KNOCKSEQUENCE weiterleiten
    for port in "${KNOCK_SEQUENCE[@]}"; do
        iptables -C INPUT -p tcp --dport $port -j KNOCKSEQUENCE 2>/dev/null || \
            iptables -A INPUT -p tcp --dport $port -j KNOCKSEQUENCE
    done
    
    log "Knock-Ketten eingerichtet"
}

# tcpdump starten, um Port-Knocks zu überwachen
start_knock_monitor() {
    # Sequenz als String für tcpdump-Filter
    sequence_filter=""
    for port in "${KNOCK_SEQUENCE[@]}"; do
        [[ -n "$sequence_filter" ]] && sequence_filter="$sequence_filter or "
        sequence_filter="${sequence_filter}tcp dst port $port"
    done
    
    log "Starte Knock-Monitor mit Filter: $sequence_filter"
    
    # tcpdump im Hintergrund starten und Ausgabe verarbeiten
    tcpdump -l -n "($sequence_filter)" 2>/dev/null | while read line; do
        process_knock "$line"
    done &
    
    # PID speichern
    echo $! > /var/run/knockd.pid
    log "Knock-Monitor gestartet mit PID $(cat /var/run/knockd.pid)"
}

# Verarbeitung der Knock-Sequenz
process_knock() {
    local packet="$1"
    
    # IP-Adresse und Port aus dem Paket extrahieren
    local ip=$(echo "$packet" | grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | head -1)
    local port=$(echo "$packet" | grep -oE "\.([0-9]+) " | grep -oE "[0-9]+" | head -1)
    
    [[ -z "$ip" || -z "$port" ]] && return
    
    log "Knock von $ip auf Port $port"
    
    # Status-Datei für diese IP
    local status_file="/tmp/knock_${ip//\./_}"
    
    # Neue Knock-Sequenz starten oder bestehende aktualisieren
    if [[ ! -f "$status_file" ]]; then
        # Prüfen, ob der erste Port in der Sequenz getroffen wurde
        if [[ "$port" -eq "${KNOCK_SEQUENCE[0]}" ]]; then
            echo "1" > "$status_file"
            log "Neue Knock-Sequenz von $ip gestartet (1/${#KNOCK_SEQUENCE[@]})"
        fi
    else
        # Aktuelle Position in der Sequenz
        local current_pos=$(cat "$status_file")
        
        # Prüfen, ob der nächste Port in der Sequenz getroffen wurde
        if [[ "$port" -eq "${KNOCK_SEQUENCE[$current_pos]}" ]]; then
            current_pos=$((current_pos + 1))
            
            # Sequenz vollständig?
            if [[ "$current_pos" -eq "${#KNOCK_SEQUENCE[@]}" ]]; then
                log "Vollständige Knock-Sequenz von $ip empfangen - öffne Port $OPEN_PORT für $ALLOW_TIME Sekunden"
                
                # Port für die IP öffnen
                iptables -A KNOCKAUTH -s "$ip" -j ACCEPT
                
                # Status-Datei löschen
                rm "$status_file"
                
                # Timer zum Schließen des Ports
                {
                    sleep $ALLOW_TIME
                    log "Schließe Port $OPEN_PORT für $ip (Zeitraum abgelaufen)"
                    iptables -D KNOCKAUTH -s "$ip" -j ACCEPT
                } &
            else
                # Nächste Position speichern
                echo "$current_pos" > "$status_file"
                log "Knock-Sequenz von $ip fortgesetzt ($current_pos/${#KNOCK_SEQUENCE[@]})"
            fi
        elif [[ "$port" -eq "${KNOCK_SEQUENCE[0]}" ]]; then
            # Sequenz neu starten, wenn der erste Port erneut angeklopft wurde
            echo "1" > "$status_file"
            log "Knock-Sequenz von $ip zurückgesetzt (1/${#KNOCK_SEQUENCE[@]})"
        else
            # Ungültiger Knock, Sequenz zurücksetzen
            rm "$status_file"
            log "Ungültiger Knock von $ip auf Port $port - Sequenz zurückgesetzt"
        fi
    fi
}

# Hauptteil des Skripts
case "$1" in
    start)
        setup_chains
        start_knock_monitor
        ;;
    stop)
        if [[ -f /var/run/knockd.pid ]]; then
            kill $(cat /var/run/knockd.pid)
            rm /var/run/knockd.pid
            log "Knock-Monitor gestoppt"
        else
            echo "Knock-Daemon läuft nicht"
        fi
        ;;
    status)
        if [[ -f /var/run/knockd.pid ]] && kill -0 $(cat /var/run/knockd.pid) 2>/dev/null; then
            echo "Knock-Daemon läuft mit PID $(cat /var/run/knockd.pid)"
        else
            echo "Knock-Daemon läuft nicht"
        fi
        ;;
    *)
        echo "Verwendung: $0 {start|stop|status}"
        exit 1
        ;;
esac

exit 0

15.7.10 Firewall-Automatisierung mit systemd

#!/bin/bash

# Skript zur Erstellung eines systemd-Dienstes für die Firewall

# Pfad zum Firewall-Skript
FIREWALL_SCRIPT="/usr/local/sbin/firewall.sh"

# systemd-Service-Datei erstellen
cat > /etc/systemd/system/firewall.service << EOF
[Unit]
Description=Firewall-Regeln
After=network.target

[Service]
Type=oneshot
ExecStart=$FIREWALL_SCRIPT start
ExecStop=$FIREWALL_SCRIPT stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

# systemd informieren
systemctl daemon-reload

# Dienst aktivieren und starten
systemctl enable firewall.service
systemctl start firewall.service

echo "Firewall-Dienst wurde erstellt und aktiviert."

15.7.11 Best Practices für Firewall-Skripte

  1. Sicherungsregeln einrichten: Stellen Sie sicher, dass Ihre Skripte eine sichere Fallback-Konfiguration haben, falls etwas schief geht.

  2. Testen vor Anwendung: Testen Sie Ihre Firewall-Skripte in einer Entwicklungsumgebung, bevor Sie sie in der Produktion einsetzen.

  3. Rollback-Mechanismus: Implementieren Sie einen Rollback-Mechanismus, der die vorherigen Regeln wiederherstellen kann, wenn die neuen Regeln Probleme verursachen.

  4. Dokumentation: Dokumentieren Sie jede Regel, insbesondere in komplexen Firewall-Konfigurationen.

  5. Modulare Struktur: Organisieren Sie Ihre Firewall-Skripte in modulare Komponenten für bessere Wartbarkeit.

  6. Regelmäßige Überprüfung: Überprüfen Sie regelmäßig Ihre Firewall-Regeln auf Redundanzen oder Sicherheitslücken.

  7. Protokollierung und Überwachung: Implementieren Sie angemessene Protokollierung und Überwachung, um potenzielle Angriffe zu erkennen.

  8. Fokus auf Standardprotokolle: Vermeiden Sie, sich auf nicht-standardisierte oder herstellerspezifische Funktionen zu verlassen.

  9. IPv6-Unterstützung: Vergessen Sie nicht, Ihre Firewall-Regeln auch für IPv6 zu konfigurieren.

  10. Prinzip der geringsten Privilegien: Erlauben Sie nur den Verkehr, der für den Betrieb notwendig ist.

  11. Automatisierte Verifizierung: Implementieren Sie Tests, die nach Regeländerungen die Funktionsfähigkeit kritischer Dienste überprüfen.

  12. Versionskontrolle: Führen Sie eine Versionskontrolle für Ihre Firewall-Skripte, um Änderungen nachverfolgen zu können.

  13. Redundanz vermeiden: Vermeiden Sie redundante Regeln, die die Leistung beeinträchtigen könnten.

  14. Regeln optimieren: Platzieren Sie häufig verwendete Regeln am Anfang der Kette, um die Verarbeitungsgeschwindigkeit zu erhöhen.

  15. Fehlerbehandlung: Implementieren Sie robuste Fehlerbehandlung in Ihren Skripten.

  16. State-Tracking begrenzen: Begrenzen Sie die Anzahl der gleichzeitigen Verbindungen im State-Tracking-System, um DoS-Angriffe zu verhindern.

  17. Outbound-Filterung: Filtern Sie nicht nur eingehenden, sondern auch ausgehenden Verkehr.

  18. Regelmäßige Aktualisierung: Aktualisieren Sie Ihre Firewall-Regeln entsprechend neuen Bedrohungen und Anforderungen.

  19. Umgebungsspezifische Regeln: Passen Sie Ihre Firewall-Regeln an die spezifische Umgebung an (Entwicklung, Test, Produktion).

  20. Notfallzugriff: Implementieren Sie Mechanismen für den Notfallzugriff, falls reguläre Zugriffskanäle blockiert sind.

15.8 Netzwerkmonitoring und -diagnose

Die effektive Überwachung und Diagnose von Netzwerken ist ein wesentlicher Bestandteil der Systemadministration. Mit Bash-Skripten können Administratoren leistungsfähige, maßgeschneiderte Monitoring-Lösungen erstellen, die kontinuierlich die Netzwerkgesundheit überwachen und bei Problemen frühzeitig alarmieren. In diesem Abschnitt werden wir verschiedene Techniken und Tools für das Netzwerkmonitoring und die Diagnose mit Bash erkunden.

15.8.1 Grundlegende Verfügbarkeitsüberwachung

15.8.1.1 Einfaches Ping-Monitoring

#!/bin/bash

# Einfaches Host-Monitoring mit Ping

HOSTS=("192.168.1.1" "server1.example.com" "10.0.0.5")
LOG_FILE="/var/log/ping_monitor.log"
TIMEOUT=2  # Timeout in Sekunden
MAX_RETRIES=3

# Sicherstellen, dass das Log-Verzeichnis existiert
mkdir -p "$(dirname "$LOG_FILE")"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

check_host() {
    local host="$1"
    local retries=0
    
    while [[ $retries -lt $MAX_RETRIES ]]; do
        if ping -c 1 -W $TIMEOUT "$host" &>/dev/null; then
            echo "$host ist erreichbar"
            log "$host ist erreichbar (Latenz: $(ping -c 1 -W $TIMEOUT "$host" | grep "time=" | cut -d= -f4) ms)"
            return 0
        fi
        
        ((retries++))
        sleep 1
    done
    
    echo "$host ist NICHT erreichbar nach $MAX_RETRIES Versuchen!" >&2
    log "$host ist NICHT erreichbar nach $MAX_RETRIES Versuchen!"
    return 1
}

log "=== Start der Ping-Überwachung ==="

for host in "${HOSTS[@]}"; do
    check_host "$host"
done

log "=== Ende der Ping-Überwachung ==="

15.8.1.2 Kontinuierliche Überwachung mit Benachrichtigung

#!/bin/bash

# Kontinuierliche Netzwerküberwachung mit E-Mail-Benachrichtigung

HOSTS_FILE="/etc/network-monitor/hosts.txt"
LOG_FILE="/var/log/network_monitor.log"
STATUS_FILE="/tmp/network_status.txt"
CHECK_INTERVAL=300  # Prüfintervall in Sekunden
ALERT_EMAIL="admin@example.com"
MAIL_PROGRAM="mail"  # Alternative: "mailx" oder "sendmail"

# Status-Codes
UP=0
DOWN=1
RECOVERED=2

# Prüfen, ob die benötigten Dateien existieren
[[ -f "$HOSTS_FILE" ]] || { echo "Fehler: Hosts-Datei $HOSTS_FILE nicht gefunden"; exit 1; }
mkdir -p "$(dirname "$LOG_FILE")"
touch "$STATUS_FILE" 2>/dev/null || { echo "Fehler: Kann Status-Datei nicht erstellen"; exit 1; }

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
    echo "$1"
}

send_alert() {
    local subject="$1"
    local message="$2"
    
    log "Sende Benachrichtigung: $subject"
    echo -e "$message" | $MAIL_PROGRAM -s "$subject" "$ALERT_EMAIL"
}

check_host() {
    local host="$1"
    local hostname="${host%%,*}"  # Format: "hostname,description"
    local description="${host#*,}"
    
    # Wenn kein Beschreibungstext vorhanden ist, verwende den Hostnamen
    [[ "$hostname" == "$description" ]] && description="$hostname"
    
    # Status des Hosts aus der Status-Datei lesen
    local previous_status=""
    if grep -q "^$hostname:" "$STATUS_FILE"; then
        previous_status=$(grep "^$hostname:" "$STATUS_FILE" | cut -d: -f2)
    else
        previous_status="UNKNOWN"
    fi
    
    # Host prüfen
    if ping -c 3 -W 2 "$hostname" &>/dev/null; then
        if [[ "$previous_status" == "DOWN" ]]; then
            # Host war vorher nicht erreichbar, jetzt aber wieder
            log "$description ($hostname) ist wieder erreichbar"
            send_alert "Server $description ist wieder ONLINE" \
                "Der Server $description ($hostname) ist nach dem Ausfall wieder erreichbar.\n\nZeitstempel: $(date)"
            # Status aktualisieren
            sed -i "/^$hostname:/d" "$STATUS_FILE"
            echo "$hostname:UP" >> "$STATUS_FILE"
            return $RECOVERED
        elif [[ "$previous_status" != "UP" ]]; then
            # Host ist neu oder war unbekannt
            log "$description ($hostname) ist erreichbar"
            # Status aktualisieren
            sed -i "/^$hostname:/d" "$STATUS_FILE"
            echo "$hostname:UP" >> "$STATUS_FILE"
            return $UP
        else
            # Host war vorher erreichbar und ist es immer noch
            return $UP
        fi
    else
        if [[ "$previous_status" != "DOWN" ]]; then
            # Host war vorher erreichbar oder unbekannt, jetzt aber nicht mehr
            log "$description ($hostname) ist NICHT erreichbar!"
            send_alert "KRITISCH: Server $description ist OFFLINE" \
                "Der Server $description ($hostname) ist nicht erreichbar.\n\nZeitstempel: $(date)"
            # Status aktualisieren
            sed -i "/^$hostname:/d" "$STATUS_FILE"
            echo "$hostname:DOWN" >> "$STATUS_FILE"
        fi
        return $DOWN
    fi
}

# Endlosschleife für kontinuierliche Überwachung
log "=== Netzwerküberwachung gestartet ==="

while true; do
    log "Starte Prüfdurchlauf..."
    
    # Status-Zusammenfassung
    up_count=0
    down_count=0
    
    # Alle Hosts prüfen
    while IFS= read -r host || [[ -n "$host" ]]; do
        # Kommentare und leere Zeilen überspringen
        [[ -z "$host" || "$host" =~ ^# ]] && continue
        
        check_host "$host"
        status=$?
        
        if [[ $status -eq $DOWN ]]; then
            ((down_count++))
        else
            ((up_count++))
        fi
    done < "$HOSTS_FILE"
    
    log "Prüfdurchlauf abgeschlossen. $up_count Hosts erreichbar, $down_count Hosts nicht erreichbar."
    
    # Warten bis zum nächsten Prüfzyklus
    sleep $CHECK_INTERVAL
done

15.8.1.3 Überwachung mit Dynamischer Anpassung an Netzwerkbedingungen

#!/bin/bash

# Adaptive Netzwerküberwachung mit dynamischem Prüfintervall

HOSTS_FILE="/etc/network-monitor/hosts.txt"
LOG_FILE="/var/log/adaptive_monitor.log"
MIN_INTERVAL=60     # Minimales Intervall in Sekunden
MAX_INTERVAL=900    # Maximales Intervall in Sekunden
CURRENT_INTERVAL=$MIN_INTERVAL
FAILURES_THRESHOLD=3  # Schwellenwert für konsekutive Fehler

# Status-Tracking
declare -A host_failures
declare -A host_last_check

# Logging-Funktion
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Anpassen des Prüfintervalls basierend auf Netzwerkstatus
adjust_interval() {
    local total_hosts=0
    local down_hosts=0
    
    for host in "${!host_failures[@]}"; do
        ((total_hosts++))
        [[ ${host_failures[$host]} -ge $FAILURES_THRESHOLD ]] && ((down_hosts++))
    done
    
    # Wenn mehr als 20% der Hosts nicht erreichbar sind, verringere das Intervall
    local down_percentage=$((down_hosts * 100 / total_hosts))
    
    if [[ $down_percentage -gt 20 ]]; then
        # Verringere das Intervall, aber nicht unter das Minimum
        CURRENT_INTERVAL=$((CURRENT_INTERVAL / 2))
        [[ $CURRENT_INTERVAL -lt $MIN_INTERVAL ]] && CURRENT_INTERVAL=$MIN_INTERVAL
        log "Netzwerkprobleme erkannt ($down_percentage% Hosts nicht erreichbar). Intervall auf $CURRENT_INTERVAL Sekunden verringert."
    elif [[ $down_percentage -eq 0 && $CURRENT_INTERVAL -lt $MAX_INTERVAL ]]; then
        # Wenn alle Hosts erreichbar sind, erhöhe das Intervall schrittweise
        CURRENT_INTERVAL=$((CURRENT_INTERVAL * 5 / 4))
        [[ $CURRENT_INTERVAL -gt $MAX_INTERVAL ]] && CURRENT_INTERVAL=$MAX_INTERVAL
        log "Netzwerk stabil. Intervall auf $CURRENT_INTERVAL Sekunden erhöht."
    fi
}

# Host-Prüfung mit adaptivem Timeout
check_host() {
    local host="$1"
    local adaptive_timeout
    
    # Letzten Check-Zeitpunkt aktualisieren
    host_last_check[$host]=$(date +%s)
    
    # Adaptiver Timeout basierend auf bisherigen Fehlerrate
    if [[ ${host_failures[$host]:-0} -gt $FAILURES_THRESHOLD ]]; then
        adaptive_timeout=5  # Längerer Timeout für problematische Hosts
    else
        adaptive_timeout=2  # Standardtimeout
    fi
    
    # Host prüfen
    if ping -c 2 -W $adaptive_timeout "$host" &>/dev/null; then
        if [[ ${host_failures[$host]:-0} -ge $FAILURES_THRESHOLD ]]; then
            log "$host ist wieder erreichbar nach ${host_failures[$host]} fehlgeschlagenen Versuchen."
        fi
        host_failures[$host]=0
        return 0
    else
        # Fehler inkrementieren
        host_failures[$host]=$((${host_failures[$host]:-0} + 1))
        
        # Bei Überschreitung des Schwellenwerts alarmieren
        if [[ ${host_failures[$host]} -eq $FAILURES_THRESHOLD ]]; then
            log "WARNUNG: $host ist seit $FAILURES_THRESHOLD Prüfungen nicht erreichbar!"
            # Hier könnte eine Benachrichtigung erfolgen
        fi
        return 1
    fi
}

# Hauptprogramm
log "=== Adaptive Netzwerküberwachung gestartet ==="

# Initialisierung
while IFS= read -r host || [[ -n "$host" ]]; do
    # Kommentare und leere Zeilen überspringen
    [[ -z "$host" || "$host" =~ ^# ]] && continue
    
    host_failures[$host]=0
    host_last_check[$host]=0
done < "$HOSTS_FILE"

# Endlosschleife
while true; do
    log "Prüfdurchlauf mit Intervall $CURRENT_INTERVAL Sekunden..."
    current_time=$(date +%s)
    
    while IFS= read -r host || [[ -n "$host" ]]; do
        # Kommentare und leere Zeilen überspringen
        [[ -z "$host" || "$host" =~ ^# ]] && continue
        
        # Prüfen, ob eine Prüfung fällig ist (basierend auf letztem Check)
        time_since_last_check=$((current_time - ${host_last_check[$host]:-0}))
        
        # Häufigere Prüfung für problematische Hosts
        check_interval=$CURRENT_INTERVAL
        [[ ${host_failures[$host]:-0} -ge $FAILURES_THRESHOLD ]] && check_interval=$((CURRENT_INTERVAL / 2))
        
        if [[ $time_since_last_check -ge $check_interval ]]; then
            log "Prüfe Host $host..."
            check_host "$host"
        fi
    done < "$HOSTS_FILE"
    
    # Intervall anpassen
    adjust_interval
    
    # Warten bis zum nächsten Zyklus
    sleep $CURRENT_INTERVAL
done

15.8.2 Tiefergehende Netzwerkdiagnose

15.8.2.1 Umfassende Verbindungsdiagnose

#!/bin/bash

# Umfassende Netzwerkdiagnose für einen Host

HOST="$1"
PORT="${2:-80}"  # Standardport 80, falls nicht angegeben
TIMEOUT=5
OUTPUT_FILE="/tmp/network_diagnosis_$(date +%Y%m%d_%H%M%S).txt"

# Prüfen, ob ein Host angegeben wurde
if [[ -z "$HOST" ]]; then
    echo "Verwendung: $0 <Hostname/IP> [Port]"
    exit 1
fi

# Hilfsfunktion zur Befehlsausführung mit Ausgabeerfassung
run_command() {
    local cmd_desc="$1"
    local cmd="$2"
    
    echo -e "\n=== $cmd_desc ===\n" >> "$OUTPUT_FILE"
    echo "Führe aus: $cmd" >> "$OUTPUT_FILE"
    
    # Befehl ausführen und Ausgabe erfassen
    local output
    output=$($cmd 2>&1)
    local status=$?
    
    echo -e "$output" >> "$OUTPUT_FILE"
    echo -e "\nStatus: $status\n" >> "$OUTPUT_FILE"
    
    # Für die Konsolenausgabe
    echo "- $cmd_desc: " 
    if [[ $status -eq 0 ]]; then
        echo "  Erfolgreich"
    else
        echo "  Fehlgeschlagen (Status $status)"
    fi
}

echo "Starte umfassende Netzwerkdiagnose für $HOST:$PORT"
echo "Die vollständige Ausgabe wird in $OUTPUT_FILE gespeichert"

# Basisdaten zum Host erfassen
echo "=== Netzwerkdiagnose für $HOST:$PORT ===" > "$OUTPUT_FILE"
echo "Datum: $(date)" >> "$OUTPUT_FILE"
echo "Ausgeführt von: $(whoami)@$(hostname)" >> "$OUTPUT_FILE"

# DNS-Auflösung prüfen
run_command "DNS-Auflösung" "host $HOST"

# IP-Adresse bestimmen
ip_address=$(host "$HOST" 2>/dev/null | grep "has address" | head -1 | awk '{print $4}')
if [[ -z "$ip_address" ]]; then
    # Versuchen, $HOST direkt als IP zu verwenden
    if [[ "$HOST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        ip_address="$HOST"
    else
        echo "Konnte IP-Adresse für $HOST nicht ermitteln" | tee -a "$OUTPUT_FILE"
    fi
fi

if [[ -n "$ip_address" ]]; then
    # Routenverfolgung
    run_command "Routenverfolgung" "traceroute -w 2 -m 15 $ip_address"
    
    # Ping-Test
    run_command "Ping-Test (5 Pakete)" "ping -c 5 -W 2 $ip_address"
    
    # MTU-Ermittlung
    run_command "MTU-Ermittlung" "ping -c 3 -M do -s 1472 $ip_address"
fi

# Port-Erreichbarkeit testen
run_command "Port-Test mit Netcat" "nc -zv -w $TIMEOUT $HOST $PORT"

# HTTPS-spezifische Tests, wenn Port 443
if [[ "$PORT" -eq 443 ]]; then
    run_command "SSL-Zertifikatsinformationen" "openssl s_client -connect $HOST:$PORT -servername $HOST </dev/null 2>/dev/null | openssl x509 -text -noout"
fi

# Whois-Informationen (für Domains oder öffentliche IPs)
run_command "WHOIS-Informationen" "whois $HOST"

# Netzwerkschnittstellen und Routing-Informationen
run_command "Lokale Netzwerkschnittstellen" "ip addr show"
run_command "Routing-Tabelle" "ip route show"

# Zusammenfassung erstellen
echo -e "\n=== Zusammenfassung der Diagnose ===\n" >> "$OUTPUT_FILE"

# DNS-Status
if grep -q "has address" "$OUTPUT_FILE"; then
    echo "DNS-Auflösung: Erfolgreich" >> "$OUTPUT_FILE"
else
    echo "DNS-Auflösung: Fehlgeschlagen" >> "$OUTPUT_FILE"
fi

# Ping-Status
if grep -q "0% packet loss" "$OUTPUT_FILE"; then
    echo "Ping: Erfolgreich (0% Paketverlust)" >> "$OUTPUT_FILE"
elif grep -q "packet loss" "$OUTPUT_FILE"; then
    packet_loss=$(grep "packet loss" "$OUTPUT_FILE" | sed -E 's/.*([0-9]+)% packet loss.*/\1/')
    echo "Ping: Teilweise erfolgreich ($packet_loss% Paketverlust)" >> "$OUTPUT_FILE"
else
    echo "Ping: Fehlgeschlagen" >> "$OUTPUT_FILE"
fi

# Port-Status
if grep -q "succeeded" "$OUTPUT_FILE"; then
    echo "Port $PORT: Offen" >> "$OUTPUT_FILE"
else
    echo "Port $PORT: Geschlossen oder blockiert" >> "$OUTPUT_FILE"
fi

echo "Diagnose abgeschlossen. Ergebnisse wurden in $OUTPUT_FILE gespeichert."

15.8.2.2 Netzwerkleistungsanalyse

#!/bin/bash

# Netzwerkleistungsanalyse-Skript

TARGET="$1"
DURATION="${2:-60}"  # Standarddauer: 60 Sekunden
OUTPUT_DIR="/var/log/network_performance"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RESULT_FILE="$OUTPUT_DIR/perf_$TARGET_$TIMESTAMP.log"

# Benötigte Tools prüfen
for tool in ping iperf traceroute; do
    if ! command -v $tool &>/dev/null; then
        echo "Fehler: $tool ist nicht installiert" >&2
        exit 1
    fi
done

# Ausgabeverzeichnis erstellen
mkdir -p "$OUTPUT_DIR"

# Einleitung
echo "=== Netzwerkleistungsanalyse für $TARGET ===" | tee "$RESULT_FILE"
echo "Zeitstempel: $(date)" | tee -a "$RESULT_FILE"
echo "Dauer: $DURATION Sekunden" | tee -a "$RESULT_FILE"
echo "" | tee -a "$RESULT_FILE"

# Ping-Test für Latenz und Jitter
echo "Führe Ping-Test durch ($DURATION Sekunden)..." | tee -a "$RESULT_FILE"
ping_output=$(ping -c $DURATION -i 1 $TARGET 2>&1)
echo "$ping_output" >> "$RESULT_FILE"

# Latenz extrahieren
avg_latency=$(echo "$ping_output" | grep "avg" | sed -E 's/.*= [0-9.]+\/([0-9.]+)\/[0-9.]+\/.*/\1/')
min_latency=$(echo "$ping_output" | grep "avg" | sed -E 's/.*= ([0-9.]+)\/[0-9.]+\/[0-9.]+\/.*/\1/')
max_latency=$(echo "$ping_output" | grep "avg" | sed -E 's/.*= [0-9.]+\/[0-9.]+\/([0-9.]+)\/.*/\1/')
packet_loss=$(echo "$ping_output" | grep -o "[0-9.]*% packet loss" | cut -d" " -f1)

# Jitter berechnen (Standardabweichung der Latenz)
jitter=$(echo "$ping_output" | grep -oE "time=[0-9.]+ ms" | cut -d= -f2 | cut -d" " -f1 | awk '{sum+=$1; sumsq+=$1*$1} END {print sqrt(sumsq/NR - (sum/NR)^2)}')

echo -e "\nLatenz-Zusammenfassung:" | tee -a "$RESULT_FILE"
echo "  Minimum: $min_latency ms" | tee -a "$RESULT_FILE"
echo "  Durchschnitt: $avg_latency ms" | tee -a "$RESULT_FILE"
echo "  Maximum: $max_latency ms" | tee -a "$RESULT_FILE"
echo "  Jitter: $jitter ms" | tee -a "$RESULT_FILE"
echo "  Paketverlust: $packet_loss" | tee -a "$RESULT_FILE"

# Bandbreitentest mit iperf, falls verfügbar
if [[ "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ || "$TARGET" =~ ^[a-zA-Z0-9][-a-zA-Z0-9]*(\.[a-zA-Z0-9][-a-zA-Z0-9]*)+$ ]]; then
    echo -e "\nFühre Bandbreitentest mit iperf durch (falls iperf-Server auf $TARGET verfügbar)..." | tee -a "$RESULT_FILE"
    iperf_output=$(iperf -c $TARGET -t 10 -i 1 2>&1 || echo "iperf-Server nicht verfügbar auf $TARGET")
    echo "$iperf_output" >> "$RESULT_FILE"
    
    if ! echo "$iperf_output" | grep -q "nicht verfügbar"; then
        bandwidth=$(echo "$iperf_output" | tail -n 1 | awk '{print $7 " " $8}')
        echo "  Durchschnittliche Bandbreite: $bandwidth" | tee -a "$RESULT_FILE"
    else
        echo "  Bandbreitentest nicht möglich (kein iperf-Server auf $TARGET)" | tee -a "$RESULT_FILE"
    fi
fi

# Routenverfolgung
echo -e "\nFühre Routenverfolgung durch..." | tee -a "$RESULT_FILE"
traceroute_output=$(traceroute -w 2 -m 20 $TARGET 2>&1)
echo "$traceroute_output" >> "$RESULT_FILE"

# Hop-Anzahl extrahieren
hop_count=$(echo "$traceroute_output" | grep -c "^ [0-9]")
echo "  Anzahl der Hops: $hop_count" | tee -a "$RESULT_FILE"

# MTU-Ermittlung durch Ping mit DF-Flag
echo -e "\nErmittle maximale MTU..." | tee -a "$RESULT_FILE"
for size in 1500 1472 1464 1400 1300 1200 1100 1000 900 800; do
    ping_mtu=$(ping -c 2 -M do -s $size $TARGET 2>&1)
    if echo "$ping_mtu" | grep -q "0% packet loss"; then
        echo "  Maximale MTU ist mindestens: $((size + 28)) Bytes" | tee -a "$RESULT_FILE"
        break
    fi
done

# Gesamtbewertung der Netzwerkleistung
echo -e "\n=== Gesamtbewertung der Netzwerkleistung ===" | tee -a "$RESULT_FILE"

# Latenz-Bewertung
if [[ $(echo "$avg_latency < 20" | bc -l) -eq 1 ]]; then
    latency_rating="Ausgezeichnet"
elif [[ $(echo "$avg_latency < 50" | bc -l) -eq 1 ]]; then
    latency_rating="Sehr gut"
elif [[ $(echo "$avg_latency < 100" | bc -l) -eq 1 ]]; then
    latency_rating="Gut"
elif [[ $(echo "$avg_latency < 200" | bc -l) -eq 1 ]]; then
    latency_rating="Mittel"
else
    latency_rating="Schlecht"
fi

# Paketverlust-Bewertung
if [[ $(echo "$packet_loss < 0.1" | bc -l) -eq 1 ]]; then
    loss_rating="Ausgezeichnet"
elif [[ $(echo "$packet_loss < 1.0" | bc -l) -eq 1 ]]; then
    loss_rating="Sehr gut"
elif [[ $(echo "$packet_loss < 3.0" | bc -l) -eq 1 ]]; then
    loss_rating="Gut"
elif [[ $(echo "$packet_loss < 10.0" | bc -l) -eq 1 ]]; then
    loss_rating="Mittel"
else
    loss_rating="Schlecht"
fi

# Jitter-Bewertung
if [[ $(echo "$jitter < 2" | bc -l) -eq 1 ]]; then
    jitter_rating="Ausgezeichnet"
elif [[ $(echo "$jitter < 5" | bc -l) -eq 1 ]]; then
    jitter_rating="Sehr gut"
elif [[ $(echo "$jitter < 10" | bc -l) -eq 1 ]]; then
    jitter_rating="Gut"
elif [[ $(echo "$jitter < 20" | bc -l) -eq 1 ]]; then
    jitter_rating="Mittel"
else
    jitter_rating="Schlecht"
fi

echo "Latenz: $latency_rating ($avg_latency ms)" | tee -a "$RESULT_FILE"
echo "Jitter: $jitter_rating ($jitter ms)" | tee -a "$RESULT_FILE"
echo "Paketverlust: $loss_rating ($packet_loss)" | tee -a "$RESULT_FILE"

echo -e "\nLeistungsanalyse abgeschlossen. Vollständiger Bericht: $RESULT_FILE"

15.8.3 Netzwerkverkehrsanalyse

15.8.3.1 Einfacher Netzwerkverkehrsmonitor

#!/bin/bash

# Einfacher Netzwerkverkehrsmonitor

INTERFACE="$1"
INTERVAL="${2:-5}"  # Standardintervall: 5 Sekunden
DURATION="${3:-60}" # Standarddauer: 60 Sekunden (0 für unbegrenzt)
LOG_FILE="/var/log/traffic_monitor.log"

# Prüfen, ob ein Netzwerkinterface angegeben wurde
if [[ -z "$INTERFACE" ]]; then
    echo "Verwendung: $0 <Interface> [Intervall in Sekunden] [Dauer in Sekunden]"
    echo "Verfügbare Interfaces:"
    ip -br link show | awk '{print "  " $1}'
    exit 1
fi

# Prüfen, ob das Interface existiert
if ! ip link show dev "$INTERFACE" &>/dev/null; then
    echo "Fehler: Interface $INTERFACE existiert nicht" >&2
    exit 1
fi

# Hilfsfunktion zur Umrechnung von Bytes in lesbare Größen
format_bytes() {
    local bytes="$1"
    local units=("B" "KB" "MB" "GB" "TB")
    local unit=0
    
    while [[ $bytes -gt 1024 && $unit -lt 4 ]]; do
        bytes=$(echo "scale=2; $bytes / 1024" | bc)
        ((unit++))
    done
    
    echo "$bytes ${units[$unit]}"
}

# Hilfsfunktion zur Berechnung der Übertragungsrate
calculate_rate() {
    local current="$1"
    local previous="$2"
    local interval="$3"
    
    local rate=$(echo "scale=2; ($current - $previous) / $interval" | bc)
    echo "$rate"
}

echo "Starte Netzwerkverkehrsmonitor für Interface $INTERFACE"
echo "Intervall: $INTERVAL Sekunden"
if [[ $DURATION -gt 0 ]]; then
    echo "Dauer: $DURATION Sekunden"
else
    echo "Dauer: Unbegrenzt (STRG+C zum Beenden)"
fi

echo "Zeitstempel,Interface,RX_Bytes,TX_Bytes,RX_Rate,TX_Rate" > "$LOG_FILE"

# Initialisierung
start_time=$(date +%s)
previous_rx=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)
previous_tx=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)
previous_time=$start_time

# Statistik-Anzeige
printf "%-20s %-15s %-15s %-15s %-15s\n" "Zeitstempel" "RX_Rate" "TX_Rate" "RX_Total" "TX_Total"
printf "%s\n" "-----------------------------------------------------------------------"

# Hauptschleife
while true; do
    current_time=$(date +%s)
    elapsed=$((current_time - start_time))
    
    # Prüfen, ob die Dauer überschritten wurde
    if [[ $DURATION -gt 0 && $elapsed -ge $DURATION ]]; then
        break
    fi
    
    # Aktuellen Verkehr lesen
    current_rx=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)
    current_tx=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)
    
    # Berechnung der Raten
    time_diff=$((current_time - previous_time))
    rx_rate=$(calculate_rate "$current_rx" "$previous_rx" "$time_diff")
    tx_rate=$(calculate_rate "$current_tx" "$previous_tx" "$time_diff")
    
    # Formatierte Werte
    rx_rate_formatted=$(format_bytes "$rx_rate")
    tx_rate_formatted=$(format_bytes "$tx_rate")
    total_rx_formatted=$(format_bytes "$current_rx")
    total_tx_formatted=$(format_bytes "$current_tx")
    timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    
    # Bildschirmausgabe
    printf "%-20s %-15s %-15s %-15s %-15s\n" \
        "$timestamp" \
        "${rx_rate_formatted}/s" \
        "${tx_rate_formatted}/s" \
        "$total_rx_formatted" \
        "$total_tx_formatted"
    
    # In Datei protokollieren
    echo "$timestamp,$INTERFACE,$current_rx,$current_tx,$rx_rate,$tx_rate" >> "$LOG_FILE"
    
    # Werte für nächste Iteration speichern
    previous_rx=$current_rx
    previous_tx=$current_tx
    previous_time=$current_time
    
    # Warten bis zum nächsten Intervall
    sleep $INTERVAL
done

echo "Netzwerkmonitor beendet. Protokoll gespeichert in $LOG_FILE"

15.8.3.2 Detaillierte Verkehrsanalyse mit tcpdump

#!/bin/bash

# Netzwerkverkehrsanalyse mit tcpdump

INTERFACE="$1"
DURATION="${2:-60}"      # Standarddauer: 60 Sekunden
OUTPUT_DIR="/var/log/network_captures"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
CAPTURE_FILE="$OUTPUT_DIR/capture_${INTERFACE}_${TIMESTAMP}.pcap"
SUMMARY_FILE="$OUTPUT_DIR/summary_${INTERFACE}_${TIMESTAMP}.txt"

# Prüfen, ob ein Netzwerkinterface angegeben wurde
if [[ -z "$INTERFACE" ]]; then
    echo "Verwendung: $0 <Interface> [Dauer in Sekunden]"
    echo "Verfügbare Interfaces:"
    ip -br link show | awk '{print "  " $1}'
    exit 1
fi

# Prüfen, ob das Interface existiert
if ! ip link show dev "$INTERFACE" &>/dev/null; then
    echo "Fehler: Interface $INTERFACE existiert nicht" >&2
    exit 1
fi

# Prüfen, ob tcpdump installiert ist
if ! command -v tcpdump &>/dev/null; then
    echo "Fehler: tcpdump ist nicht installiert" >&2
    exit 1
fi

# Ausgabeverzeichnis erstellen
mkdir -p "$OUTPUT_DIR"

echo "Starte Netzwerkverkehrsanalyse für Interface $INTERFACE"
echo "Dauer: $DURATION Sekunden"
echo "Erfasse Verkehr in $CAPTURE_FILE..."

# Verkehr erfassen
tcpdump -i "$INTERFACE" -w "$CAPTURE_FILE" -G "$DURATION" -W 1 &
TCPDUMP_PID=$!

# Fortschrittsanzeige
total_seconds=$DURATION
for ((i=1; i<=total_seconds; i++)); do
    percent=$((i * 100 / total_seconds))
    completed=$((percent / 2))
    remaining=$((50 - completed))
    
    progress="["
    for ((j=0; j<completed; j++)); do progress+="#"; done
    for ((j=0; j<remaining; j++)); do progress+="-"; done
    progress+="] $percent%"
    
    echo -ne "$progress\r"
    sleep 1
done
echo

# Warten, bis tcpdump beendet ist
wait $TCPDUMP_PID

echo "Verkehrserfassung abgeschlossen"
echo "Analysiere erfassten Verkehr..."

# Statistiken erstellen
echo "=== Netzwerkverkehrsanalyse für $INTERFACE ===" > "$SUMMARY_FILE"
echo "Zeitstempel: $(date)" >> "$SUMMARY_FILE"
echo "Erfassungsdauer: $DURATION Sekunden" >> "$SUMMARY_FILE"
echo "" >> "$SUMMARY_FILE"

# Protokollverteilung
echo "=== Protokollverteilung ===" >> "$SUMMARY_FILE"
tcpdump -r "$CAPTURE_FILE" -nn -q | awk '{print $2}' | cut -d: -f1 | sort | uniq -c | sort -nr >> "$SUMMARY_FILE"

# Top-Quell-IPs
echo "" >> "$SUMMARY_FILE"
echo "=== Top 10 Quell-IPs ===" >> "$SUMMARY_FILE"
tcpdump -r "$CAPTURE_FILE" -nn 'ip' | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -nr | head -10 >> "$SUMMARY_FILE"

# Top-Ziel-IPs
echo "" >> "$SUMMARY_FILE"
echo "=== Top 10 Ziel-IPs ===" >> "$SUMMARY_FILE"
tcpdump -r "$CAPTURE_FILE" -nn 'ip' | awk '{print $5}' | cut -d. -f1-4 | cut -d: -f1 | sort | uniq -c | sort -nr | head -10 >> "$SUMMARY_FILE"

# TCP-Ports
echo "" >> "$SUMMARY_FILE"
echo "=== Top 10 TCP-Ports ===" >> "$SUMMARY_FILE"
tcpdump -r "$CAPTURE_FILE" -nn 'tcp' | awk '{print $5}' | cut -d. -f5 | sort | uniq -c | sort -nr | head -10 >> "$SUMMARY_FILE"

# UDP-Ports
echo "" >> "$SUMMARY_FILE"
echo "=== Top 10 UDP-Ports ===" >> "$SUMMARY_FILE"
tcpdump -r "$CAPTURE_FILE" -nn 'udp' | awk '{print $5}' | cut -d. -f5 | sort | uniq -c | sort -nr | head -10 >> "$SUMMARY_FILE"

# Paketgrößenverteilung
echo "" >> "$SUMMARY_FILE"
echo "=== Paketgrößenverteilung ===" >> "$SUMMARY_FILE"
tcpdump -r "$CAPTURE_FILE" -nn | awk '{print $NF}' | grep -o '[0-9]*' | sort -n | uniq -c | awk '
    BEGIN {
        small=0; medium=0; large=0; jumbo=0;
    }
    {
        if ($2 < 128) small += $1;
        else if ($2 < 512) medium += $1;
        else if ($2 < 1500) large += $1;
        else jumbo += $1;
    }
    END {
        total = small + medium + large + jumbo;
        printf "Kleine Pakete (<128 Bytes): %d (%.2f%%)\n", small, (small/total*100);
        printf "Mittlere Pakete (128-511 Bytes): %d (%.2f%%)\n", medium, (medium/total*100);
        printf "Große Pakete (512-1499 Bytes): %d (%.2f%%)\n", large, (large/total*100);
        printf "Jumbo-Pakete (>=1500 Bytes): %d (%.2f%%)\n", jumbo, (jumbo/total*100);
    }
' >> "$SUMMARY_FILE"

echo "Analyse abgeschlossen"
echo "Zusammenfassung gespeichert in $SUMMARY_FILE"
echo "Rohe Erfassungsdaten gespeichert in $CAPTURE_FILE"

# Kurze Zusammenfassung anzeigen
echo ""
echo "=== Kurze Zusammenfassung ==="
echo "Gesamtzahl der Pakete: $(tcpdump -r "$CAPTURE_FILE" -nn | wc -l)"
echo "Protokollverteilung: "
tcpdump -r "$CAPTURE_FILE" -nn -q | awk '{print $2}' | cut -d: -f1 | sort | uniq -c | sort -nr | head -5
echo ""
echo "Top 5 Quell-IPs:"
tcpdump -r "$CAPTURE_FILE" -nn 'ip' | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -nr | head -5
echo ""
echo "Top 5 Ziel-IPs:"
tcpdump -r "$CAPTURE_FILE" -nn 'ip' | awk '{print $5}' | cut -d. -f1-4 | cut -d: -f1 | sort | uniq -c | sort -nr | head -5

15.8.4 Überwachung von Netzwerkdiensten

15.8.4.1 Dienstverfügbarkeitsmonitor

#!/bin/bash

# Dienstverfügbarkeitsmonitor

CONFIG_FILE="/etc/services_monitor/config.json"
LOG_FILE="/var/log/service_monitor.log"
STATUS_FILE="/var/log/service_status.json"
CHECK_INTERVAL=300  # 5 Minuten

# Prüfen, ob jq installiert ist
if ! command -v jq &>/dev/null; then
    echo "Fehler: jq ist nicht installiert (benötigt für JSON-Verarbeitung)" >&2
    exit 1
fi

# Prüfen, ob die Konfigurationsdatei existiert
if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "Fehler: Konfigurationsdatei $CONFIG_FILE nicht gefunden" >&2
    exit 1
fi

# Verzeichnisse erstellen
mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$STATUS_FILE")"

# Logging-Funktion
log() {
    local level="$1"
    local message="$2"
    echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $message" | tee -a "$LOG_FILE"
}

# HTTP-Dienst prüfen
check_http() {
    local name="$1"
    local url="$2"
    local expected_status="${3:-200}"
    local timeout="${4:-10}"
    
    local start_time=$(date +%s.%N)
    local status_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$timeout" "$url")
    local end_time=$(date +%s.%N)
    local response_time=$(echo "$end_time - $start_time" | bc)
    
    if [[ "$status_code" == "$expected_status" ]]; then
        log "INFO" "Dienst $name ist VERFÜGBAR (Status: $status_code, Zeit: ${response_time}s)"
        return 0
    else
        log "ERROR" "Dienst $name ist NICHT VERFÜGBAR (Status: $status_code, erwartet: $expected_status)"
        return 1
    fi
}

# TCP-Dienst prüfen
check_tcp() {
    local name="$1"
    local host="$2"
    local port="$3"
    local timeout="${4:-5}"
    
    local start_time=$(date +%s.%N)
    if timeout "$timeout" bash -c "exec 3<>/dev/tcp/$host/$port" 2>/dev/null; then
        exec 3<&-  # Socket schließen
        local end_time=$(date +%s.%N)
        local response_time=$(echo "$end_time - $start_time" | bc)
        log "INFO" "Dienst $name ist VERFÜGBAR (TCP: $host:$port, Zeit: ${response_time}s)"
        return 0
    else
        log "ERROR" "Dienst $name ist NICHT VERFÜGBAR (TCP: $host:$port)"
        return 1
    fi
}

# ICMP/Ping prüfen
check_ping() {
    local name="$1"
    local host="$2"
    local timeout="${3:-2}"
    local count="${4:-3}"
    
    if ping -c "$count" -W "$timeout" "$host" &>/dev/null; then
        local response_time=$(ping -c 1 -W "$timeout" "$host" | grep "time=" | cut -d= -f4 | cut -d" " -f1)
        log "INFO" "Dienst $name ist VERFÜGBAR (Ping: $host, Zeit: ${response_time}ms)"
        return 0
    else
        log "ERROR" "Dienst $name ist NICHT VERFÜGBAR (Ping: $host)"
        return 1
    fi
}

# DNS-Auflösung prüfen
check_dns() {
    local name="$1"
    local domain="$2"
    local dns_server="${3:-8.8.8.8}"
    local timeout="${4:-3}"
    
    local start_time=$(date +%s.%N)
    if dig "@$dns_server" "$domain" +short +time="$timeout" &>/dev/null; then
        local end_time=$(date +%s.%N)
        local response_time=$(echo "$end_time - $start_time" | bc)
        local resolved_ip=$(dig "@$dns_server" "$domain" +short +time="$timeout" | head -1)
        log "INFO" "Dienst $name ist VERFÜGBAR (DNS: $domain -> $resolved_ip, Zeit: ${response_time}s)"
        return 0
    else
        log "ERROR" "Dienst $name ist NICHT VERFÜGBAR (DNS: $domain @ $dns_server)"
        return 1
    fi
}

# Aktualisieren der Status-Datei
update_status() {
    local name="$1"
    local status="$2"
    local response_time="$3"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    # Status-Datei initialisieren, falls nicht vorhanden
    if [[ ! -f "$STATUS_FILE" ]]; then
        echo "{}" > "$STATUS_FILE"
    fi
    
    # Aktuellen Status lesen
    local current_status=$(jq -r --arg name "$name" '.[$name].status // "unknown"' "$STATUS_FILE")
    
    # Status-Änderung erkennen
    if [[ "$current_status" != "$status" && "$current_status" != "unknown" ]]; then
        if [[ "$status" == "up" ]]; then
            log "INFO" "Dienst $name ist wieder VERFÜGBAR nach Ausfall"
        elif [[ "$status" == "down" ]]; then
            log "ALERT" "Dienst $name ist AUSGEFALLEN"
            # Hier könnte eine Benachrichtigung erfolgen
        fi
    fi
    
    # Status-Datei aktualisieren
    jq --arg name "$name" \
       --arg status "$status" \
       --arg time "$response_time" \
       --arg ts "$timestamp" \
       '.[$name] = {status: $status, last_check: $ts, response_time: $time}' \
       "$STATUS_FILE" > "${STATUS_FILE}.tmp" && mv "${STATUS_FILE}.tmp" "$STATUS_FILE"
}

# Hauptfunktion zum Prüfen aller Dienste
check_all_services() {
    log "INFO" "Starte Prüfung aller Dienste..."
    
    # Prüfen, ob die Konfigurationsdatei valides JSON enthält
    if ! jq -e . "$CONFIG_FILE" >/dev/null 2>&1; then
        log "ERROR" "Konfigurationsdatei enthält ungültiges JSON"
        return 1
    fi
    
    # Alle Dienste aus der Konfiguration prüfen
    jq -c '.services[]' "$CONFIG_FILE" | while read -r service; do
        local name=$(echo "$service" | jq -r '.name')
        local type=$(echo "$service" | jq -r '.type')
        local params=$(echo "$service" | jq -r '.params | @sh' | tr -d "'")
        
        log "INFO" "Prüfe Dienst: $name (Typ: $type)"
        
        case "$type" in
            http)
                eval "check_http $name $params"
                status=$?
                ;;
            tcp)
                eval "check_tcp $name $params"
                status=$?
                ;;
            ping)
                eval "check_ping $name $params"
                status=$?
                ;;
            dns)
                eval "check_dns $name $params"
                status=$?
                ;;
            *)
                log "ERROR" "Unbekannter Dienst-Typ: $type für $name"
                status=2
                ;;
        esac
        
        # Status aktualisieren
        if [[ $status -eq 0 ]]; then
            # Antwortzeit extrahieren (aus dem letzten Log-Eintrag)
            local response_time=$(tail -n 1 "$LOG_FILE" | grep -o "Zeit: [0-9.]*" | cut -d" " -f2)
            update_status "$name" "up" "$response_time"
        else
            update_status "$name" "down" "0"
        fi
    done
    
    log "INFO" "Prüfung aller Dienste abgeschlossen"
}

# Kontinuierliche Überwachung
log "INFO" "Starte Dienstverfügbarkeitsmonitor"

if [[ "$1" == "once" ]]; then
    # Einmalige Prüfung
    check_all_services
else
    # Kontinuierliche Prüfung
    while true; do
        check_all_services
        log "INFO" "Warte $CHECK_INTERVAL Sekunden bis zur nächsten Prüfung..."
        sleep $CHECK_INTERVAL
    done
fi

15.8.4.2 Beispiel-Konfiguration für Dienstverfügbarkeitsmonitor

{
  "services": [
    {
      "name": "Website",
      "type": "http",
      "params": "https://example.com 200 10"
    },
    {
      "name": "API-Dienst",
      "type": "http",
      "params": "https://api.example.com/status 200 5"
    },
    {
      "name": "Datenbank",
      "type": "tcp",
      "params": "db.example.com 3306 3"
    },
    {
      "name": "Mail-Server",
      "type": "tcp",
      "params": "mail.example.com 25 5"
    },
    {
      "name": "Gateway",
      "type": "ping",
      "params": "gateway.example.com 2 3"
    },
    {
      "name": "DNS-Auflösung",
      "type": "dns",
      "params": "example.com 8.8.8.8 3"
    }
  ]
}

15.8.5 Integration von Netzwerkmonitoring-Tools

15.8.5.1 Automatisierte Wireshark-Erfassung

#!/bin/bash

# Automatisierte Wireshark/tshark-Erfassung für Netzwerkanalyse

INTERFACE="$1"
DURATION="${2:-300}"      # Standarddauer: 5 Minuten
FILTER="${3:-}"           # Optionaler Paketfilter
OUTPUT_DIR="/var/captures"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
CAPTURE_FILE="$OUTPUT_DIR/capture_${TIMESTAMP}.pcapng"
STATS_FILE="$OUTPUT_DIR/stats_${TIMESTAMP}.txt"

# Prüfen, ob tshark installiert ist
if ! command -v tshark &>/dev/null; then
    echo "Fehler: tshark ist nicht installiert" >&2
    echo "Bitte installieren Sie es mit: sudo apt-get install tshark" >&2
    exit 1
fi

# Prüfen, ob ein Interface angegeben wurde
if [[ -z "$INTERFACE" ]]; then
    echo "Verwendung: $0 <Interface> [Dauer in Sekunden] [Paketfilter]"
    echo "Verfügbare Interfaces:"
    tshark -D
    exit 1
fi

# Ausgabeverzeichnis erstellen
mkdir -p "$OUTPUT_DIR"

echo "Starte Netzwerkerfassung auf Interface $INTERFACE"
echo "Dauer: $DURATION Sekunden"
if [[ -n "$FILTER" ]]; then
    echo "Filter: $FILTER"
fi
echo "Ausgabedatei: $CAPTURE_FILE"

# Erfassung mit tshark
if [[ -n "$FILTER" ]]; then
    tshark -i "$INTERFACE" -w "$CAPTURE_FILE" -a duration:"$DURATION" -f "$FILTER"
else
    tshark -i "$INTERFACE" -w "$CAPTURE_FILE" -a duration:"$DURATION"
fi

echo "Erfassung abgeschlossen. Erstelle Statistiken..."

# Protokollverteilung
echo "=== Protokollverteilung ===" > "$STATS_FILE"
tshark -r "$CAPTURE_FILE" -q -z io,phs >> "$STATS_FILE"

# Konversationsstatistiken
echo -e "\n=== IP-Konversationen ===" >> "$STATS_FILE"
tshark -r "$CAPTURE_FILE" -q -z conv,ip >> "$STATS_FILE"

echo -e "\n=== TCP-Konversationen ===" >> "$STATS_FILE"
tshark -r "$CAPTURE_FILE" -q -z conv,tcp >> "$STATS_FILE"

echo -e "\n=== UDP-Konversationen ===" >> "$STATS_FILE"
tshark -r "$CAPTURE_FILE" -q -z conv,udp >> "$STATS_FILE"

# HTTP-Anfragen (falls vorhanden)
echo -e "\n=== HTTP-Anfragen ===" >> "$STATS_FILE"
tshark -r "$CAPTURE_FILE" -Y "http.request" -T fields -e http.host -e http.request.uri | sort | uniq -c | sort -nr >> "$STATS_FILE"

# DNS-Anfragen (falls vorhanden)
echo -e "\n=== DNS-Anfragen ===" >> "$STATS_FILE"
tshark -r "$CAPTURE_FILE" -Y "dns.qry.name" -T fields -e dns.qry.name | sort | uniq -c | sort -nr >> "$STATS_FILE"

# Exportstatistiken
echo -e "\n=== I/O-Statistiken (pro Sekunde) ===" >> "$STATS_FILE"
tshark -r "$CAPTURE_FILE" -q -z io,stat,1 >> "$STATS_FILE"

echo "Analyse abgeschlossen. Statistiken gespeichert in $STATS_FILE"
echo "Die Erfassungsdatei kann mit Wireshark geöffnet werden: wireshark $CAPTURE_FILE"

15.8.6 Visualisierung von Netzwerkdaten

15.8.6.1 Einfacher Netzwerktraffic-Graph

#!/bin/bash

# Einfacher Netzwerktraffic-Graph mit gnuplot

INTERFACE="$1"
DURATION="${2:-300}"    # Standarddauer: 5 Minuten
INTERVAL="${3:-5}"      # Standardintervall: 5 Sekunden
OUTPUT_DIR="/var/log/network_graphs"
OUTPUT_FILE="$OUTPUT_DIR/traffic_$(date +%Y%m%d_%H%M%S)"
DATA_FILE="${OUTPUT_FILE}.dat"
PLOT_FILE="${OUTPUT_FILE}.png"

# Prüfen, ob gnuplot installiert ist
if ! command -v gnuplot &>/dev/null; then
    echo "Fehler: gnuplot ist nicht installiert" >&2
    echo "Bitte installieren Sie es mit: sudo apt-get install gnuplot" >&2
    exit 1
fi

# Prüfen, ob ein Interface angegeben wurde
if [[ -z "$INTERFACE" ]]; then
    echo "Verwendung: $0 <Interface> [Dauer in Sekunden] [Intervall in Sekunden]"
    echo "Verfügbare Interfaces:"
    ip -br link show | awk '{print "  " $1}'
    exit 1
fi

# Prüfen, ob das Interface existiert
if ! ip link show dev "$INTERFACE" &>/dev/null; then
    echo "Fehler: Interface $INTERFACE existiert nicht" >&2
    exit 1
fi

# Ausgabeverzeichnis erstellen
mkdir -p "$OUTPUT_DIR"

# Hilfsfunktion zur Umrechnung von Bytes in Bit/s
bytes_to_bits() {
    local bytes="$1"
    local interval="$2"
    echo "$(( bytes * 8 / interval ))"
}

echo "Erfasse Netzwerktraffic auf Interface $INTERFACE..."
echo "Dauer: $DURATION Sekunden, Intervall: $INTERVAL Sekunden"
echo "Ausgabedatei: $PLOT_FILE"

# Spaltenbeschriftung in die Datendatei schreiben
echo "# Zeit RX(bit/s) TX(bit/s)" > "$DATA_FILE"

# Initialisierung
start_time=$(date +%s)
previous_rx=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)
previous_tx=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)
previous_time=$start_time

# Datenerfassung
while true; do
    current_time=$(date +%s)
    elapsed=$((current_time - start_time))
    
    # Prüfen, ob die Dauer überschritten wurde
    if [[ $elapsed -ge $DURATION ]]; then
        break
    fi
    
    # Aktuellen Verkehr lesen
    current_rx=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)
    current_tx=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)
    
    # Berechnung der Raten
    time_diff=$((current_time - previous_time))
    rx_bytes=$((current_rx - previous_rx))
    tx_bytes=$((current_tx - previous_tx))
    
    rx_bits_per_sec=$(bytes_to_bits $rx_bytes $time_diff)
    tx_bits_per_sec=$(bytes_to_bits $tx_bytes $time_diff)
    
    # In Datei schreiben
    echo "$elapsed $rx_bits_per_sec $tx_bits_per_sec" >> "$DATA_FILE"
    
    # Werte für nächste Iteration speichern
    previous_rx=$current_rx
    previous_tx=$current_tx
    previous_time=$current_time
    
    # Fortschrittsanzeige
    percent=$((elapsed * 100 / DURATION))
    completed=$((percent / 2))
    remaining=$((50 - completed))
    
    progress="["
    for ((j=0; j<completed; j++)); do progress+="#"; done
    for ((j=0; j<remaining; j++)); do progress+="-"; done
    progress+="] $percent%"
    
    echo -ne "$progress\r"
    
    # Warten bis zum nächsten Intervall
    sleep $INTERVAL
done
echo

echo "Datenerfassung abgeschlossen. Erstelle Graph..."

# Gnuplot-Skript erstellen
gnuplot_script=$(mktemp)
cat > "$gnuplot_script" << EOF
set terminal pngcairo enhanced font "sans,10" size 1200,800
set output "$PLOT_FILE"
set title "Netzwerkverkehr für Interface $INTERFACE" font ",14"
set xlabel "Zeit (Sekunden)"
set ylabel "Datenrate (bit/s)"
set grid
set key outside right top
set format y "%.1s%c"

# Farbdefinitionen
set style line 1 lc rgb "#0060ad" lw 2
set style line 2 lc rgb "#dd181f" lw 2

# Glätten der Kurven
set smooth bezier

plot "$DATA_FILE" using 1:2 with lines ls 1 title "RX (Download)", \
     "$DATA_FILE" using 1:3 with lines ls 2 title "TX (Upload)"
EOF

# Graph erstellen
gnuplot "$gnuplot_script"
rm "$gnuplot_script"

echo "Graph erstellt: $PLOT_FILE"

# Anzeigen des Bildes, falls möglich
if command -v display &>/dev/null; then
    display "$PLOT_FILE" &
elif command -v xdg-open &>/dev/null; then
    xdg-open "$PLOT_FILE" &
else
    echo "Sie können den Graph unter $PLOT_FILE betrachten"
fi