Zum Inhalt

IMConnect DICOM-Router – Betriebshandbuch

Dieses Handbuch richtet sich an Anwenderinnen und Anwender, die den DICOM-Router konfigurieren und betreiben – nicht an Entwickler. Es erklärt den Aufbau der Konfigurationsdatei dicom-router.yaml Schritt für Schritt und den laufenden Betrieb.

Weiterführende Detail-Konfiguration einzelner Plugins: Plugin-Referenz.


1. Überblick

Der DICOM-Router nimmt eingehende DICOM-Verbindungen an und leitet sie anhand von Filter-Regeln an definierte Ziele weiter. Optional kann er unterwegs Nachrichten umschreiben, zu einem zusätzlichen Empfänger abzweigen oder mitprotokollieren.

Vier Kernbegriffe beschreiben, wie der Router arbeitet:

Begriff Bedeutung
Listener Der Port und der AE-Titel, unter dem der Router selbst erreichbar ist.
Filter Geordnete Regeln, die entscheiden, was mit einer Verbindung passiert.
Actions Optionale Verarbeitungsschritte (Plugins), die eine Regel anstösst.
Destinations Benannte Ziele: entweder weiterleiten oder die Verbindung ablehnen.

Vereinfachter Datenfluss einer Verbindung:

   Gerät (Modalität/PACS)
          │  DICOM-Verbindung
   ┌──────────────┐
   │   Listener   │   nimmt die Verbindung an
   └──────┬───────┘
   ┌──────────────┐
   │    Filter    │   erste passende Regel gewinnt
   └──────┬───────┘
     ┌────┴─────┐
     ▼          ▼
  Actions    Destination
 (optional)   ┌──────────────┬───────────────┐
              ▼              ▼
          forward          reject
     (an Ziel weiterleiten) (Verbindung ablehnen)
        Ziel-System (z.B. PACS)

Für jede Verbindung sucht der Router die erste passende Filter-Regel, führt deren Actions aus (falls konfiguriert) und leitet die Verbindung an die in der Regel genannte Destination weiter – oder lehnt sie sauber ab.


2. Installation & erste Schritte

Dieser Abschnitt ist bewusst kurz. Die vollständige Schritt-für-Schritt-Anleitung (dedizierter Benutzer, Verzeichnisse, Rechte) steht in der Kopfzeile der mitgelieferten systemd-Unit und in der Deployment-Doku.

Der Router wird als .tar.gz ausgeliefert. Grober Ablauf einer Erstinstallation:

  1. Entpacken in das App-Verzeichnis (z.B. /opt/imconnect):
    tar -xzf dicom-router-X.Y.Z.tar.gz --strip-components=1 -C /opt/imconnect
    
  2. Konfiguration anlegen: Das Verzeichnis config/ gehört dem Betreiber und wird bei Updates nie überschrieben. Die Vorlagen liegen unter share/ mit der Endung .example und werden einmalig kopiert:
    mkdir -p /opt/imconnect/config
    cp /opt/imconnect/share/dicom-router.yaml.example /opt/imconnect/config/dicom-router.yaml
    cp /opt/imconnect/share/logback.xml.example       /opt/imconnect/config/logback.xml
    cp /opt/imconnect/share/dicom-router.env.example  /opt/imconnect/config/dicom-router.env
    
    Danach dicom-router.yaml an die eigene Umgebung anpassen (siehe Abschnitt 3). Zugangsdaten zur Datenbank liegen in dicom-router.env (chmod 600).
  3. systemd-Unit installieren (Vorlage dicom-router.service.example) und starten:
    systemctl enable --now dicom-router
    
  4. Prüfen, ob der Router läuft:
    dicom-router-ctl status
    

3. Die Konfigurationsdatei dicom-router.yaml

Die aktive Konfiguration liegt unter /opt/imconnect/config/dicom-router.yaml. Sie ist in Abschnitte gegliedert. Für den normalen Betrieb sind vor allem router, destinations, filter und actions relevant.

3.1 router – Listener und allgemeine Einstellungen

router:
  listener:
    host: 0.0.0.0
    port: 11112
    aet: IMCONNECT
    tcplog: false
    socketTimeout: 120000
    connectTimeout: 10000

  max-pdu-length: 131072
  storage-path: /var/tmp/dicom-cache
  log-folder: /var/log/imconnect/pdu-dumps

router.listener – so ist der Router selbst erreichbar:

Feld Bedeutung
host Adresse, auf der gelauscht wird. 0.0.0.0 = alle Netzwerkkarten.
port Port, auf dem DICOM-Verbindungen angenommen werden (Standard 11112).
aet Der eigene AE-Titel des Routers.
tcplog Schreibt jedes empfangene und gesendete TCP-Paket dieses Listeners vollständig ins Programm-Log (true/false, Vorlage: false). Nur zur Fehlersuche — siehe den Hinweis unter der Tabelle.
socketTimeout Zeitlimit einer inaktiven Verbindung in Millisekunden (fehlt das Feld, gilt 60000; die Vorlagendatei setzt 120000). Derselbe Wert begrenzt auch, wie lange ein einzelner Schreibvorgang zum Client oder zum Ziel dauern darf — eine Gegenstelle, die nichts mehr abholt, blockiert den Router sonst unbegrenzt.
connectTimeout Zeitlimit in Millisekunden für den Verbindungsaufbau zu einem Ziel — Namensauflösung und TCP-Handshake zusammen (fehlt das Feld, gilt 10000). Ist ein Ziel nicht erreichbar, gibt der Router nach dieser Zeit auf, statt auf das Zeitlimit des Betriebssystems zu warten (unter Linux bis zu zwei Minuten), während die Gegenstelle ohne Antwort wartet.

tcplog nur zur Fehlersuche einschalten. Die Ausgabe enthält den Paketinhalt vollständig und damit auch Patientendaten im Klartext (Name, Patienten-ID). Das Programm-Log wird nach Grösse und Alter rotiert und ist nicht als Ablage für Patientendaten gedacht; ausserdem füllt ein Eintrag pro TCP-Paket die Logdateien bei Last innerhalb von Minuten. Zum Analysieren einschalten, danach wieder ausschalten (beides ohne Neustart), und die Logdateien der Zeit dazwischen entsprechend behandeln. Für die dauerhafte Nachvollziehbarkeit ist der Datenbank-Audit-Trail da.

Weitere router-Einstellungen, die ein Betreiber ggf. anpasst:

Feld Bedeutung
max-pdu-length Maximale DICOM-Paketgrösse (PDU), die der Router den Gegenstellen anbietet und durchsetzt. Erlaubt sind Werte von 4096 bis 4194304 (4 MB); die Vorlagendatei liefert 131072 (128 KB). Grössere Werte reduzieren den Protokoll-Overhead bei grossen Objekten; Werte über 1 MB sind nicht empfohlen (solche Pakete puffert der Router auf die Festplatte statt im Speicher). Die Gegenstelle handelt unabhängig davon ihr eigenes Maximum aus — effektiv gilt je Richtung der kleinere Wert.
storage-path Verzeichnis für den internen Zwischenspeicher grosser Objekte. Muss beschreibbar sein.
log-folder Verzeichnis für binäre PDU-Mitschnitte (nur bei aktiviertem logLevel 5/6/7, siehe Abschnitt 5). Nicht die normalen Anwendungslogs.

Die übrigen router-Felder (Speicher-Schwellen usw.) sind technische Feinjustierung und in der Vorlagendatei kommentiert – im Normalfall unverändert lassen.

3.2 destinations – Ziele

Ziele sind benannt und werden aus Filter-Regeln über ihren Namen referenziert. Es gibt zwei Typen.

Weiterleiten (type: forward) – der Regelfall. type darf weggelassen werden, da forward der Standard ist:

destinations:
  pacs1:
    host: pacs1.example.org
    port: 104
    aet: PACS
Feld Bedeutung
host Ziel-Rechner (Hostname oder IP).
port Ziel-Port.
aet AE-Titel des Ziel-Systems.

Ablehnen (type: reject) – weist die Verbindung sauber mit einer DICOM-Ablehnung (A-ASSOCIATE-RJ) zurück, statt sie weiterzuleiten:

destinations:
  reject_unknown:
    type: reject
    result: 1                   # rejected-permanent
    source: 1                   # service-user
    reason: 7                   # called-aet-not-recognized
    message: "Unbekannter AE-Titel  Verbindung abgelehnt"

Die Felder result, source und reason sind DICOM-Codes. Für die meisten Fälle genügt eine dieser gängigen Kombinationen:

Zweck result source reason
Unbekannter Ziel-AE-Titel (called AET) 1 1 7
Unbekannter Absender-AE-Titel (calling AET) 1 1 3
Generelle Ablehnung ohne näheren Grund 1 1 1
Vorübergehend keine Kapazität (transient) 2 3 1
  • result: 1 = dauerhaft abgelehnt, 2 = vorübergehend abgelehnt.
  • source: 1 = Service-User, 2 = ACSE, 3 = Presentation.
  • reason: Bedeutung hängt von source ab (siehe Codes oben).
  • message: freier Text, erscheint nur im Log.

Den reason passend zum Ablehnungsgrund wählen: Wird nach dem Absender gefiltert (unbekanntes Gerät, Auffang-Regel über callingAET), ist reason: 3 der korrekte Code; reason: 7 passt, wenn der angefragte AE-Titel nicht existiert; im Zweifel ist reason: 1 (kein näherer Grund) immer zulässig.

3.3 filter – Routing-Regeln

filter ist eine geordnete Liste. Der Router prüft die Regeln von oben nach unten und wendet die erste an, die passt (first match wins). Danach wird nicht weitergesucht. Deshalb: spezifische Regeln nach oben, eine Auffang-Regel ganz nach unten.

Die wichtigsten Felder einer Regel:

Feld Bedeutung
callingAET AE-Titel des Absenders (Gerät). * oder weglassen = beliebig.
calledAET Angefragter Ziel-AE-Titel. * oder weglassen = beliebig.
logLevel Protokollierungsstufe für diese Verbindung (siehe Abschnitt 5).
actions Liste von Action-Namen, die für diese Regel ausgeführt werden (optional).
destination Das Ziel der Regel: ein Name aus destinations: (weiterleiten oder ablehnen).

Hinweis: Das YAML-Feld destination nimmt formal eine Liste an, unterstützt aber genau ein Ziel — nur der erste Eintrag wird verwendet. Die Listenform existiert, damit die Konfigurationsstruktur stabil bleibt, falls künftig mehrere Ziele möglich werden. Also: immer genau einen Eintrag angeben, z.B. destination: ["pacs1"].

Zusätzlich lässt sich eine Regel über Netzwerk-Merkmale einschränken (clientLocalIp, routerLocalPort usw.); ohne Angabe gilt jeweils „beliebig" (IP 0.0.0.0/0, Port 0).

AE-Titel-Vergleich: Werte werden standardmässig exakt verglichen (Gross-/Kleinschreibung beachten); nur der Wert * bedeutet „beliebig". Mit dem Präfix regex: wird der Wert stattdessen als regulärer Ausdruck ausgewertet und muss den ganzen AE-Titel abdecken:

callingAET: "regex:^US[0-9]+$"   # eine Regel für alle Geräte US1, US2, US3, ...

Ein ungültiger Ausdruck ist ein Konfigurationsfehler und verhindert Start bzw. reload.

Beispiel 1 – ein Gerät an ein PACS, alles andere ablehnen:

filter:
  - callingAET: US1
    calledAET: PACS
    destination:
      - "pacs1"

  # Auffang-Regel: alles Übrige ablehnen
  - callingAET: "*"
    calledAET: "*"
    destination:
      - "reject_unknown"

Beispiel 2 – mehrere gleichartige Geräte über einen regulären Ausdruck:

filter:
  - callingAET: "regex:^US[0-9]+$"   # US1, US2, ...
    calledAET: PACS
    destination:
      - "pacs1"
  - callingAET: "*"
    destination:
      - "reject_unknown"

Beispiel 3 – Regel mit AE-Titel-Umschreibung vor dem Weiterleiten:

filter:
  - callingAET: MRI1
    calledAET: PACS
    actions:
      - "aetRewrite_mri"   # in actions definiert, siehe unten
    destination:
      - "pacs1"

3.4 actions – Verarbeitungsschritte (Plugins)

Unter actions werden benannte Verarbeitungsschritte definiert. Ein Name verweist auf eine Liste von Plugin-Einträgen; die Parameter eines Plugins stehen in seinem config:-Block. Eine Regel aktiviert eine Action, indem sie deren Namen unter actions nennt (siehe Beispiel 3 oben).

Grundaufbau:

actions:
  aetRewrite_mri:
    - plugin: <Plugin-Kennung – siehe Plugin-Referenz>
      config:
        # plugin-spezifische Parameter
        enabled: true

Welche Plugins verfügbar sind und welche Parameter ihr config:-Block kennt (z.B. AE-Titel-Umschreibung, Protokoll-Mitschnitt, Q/R-Feldanpassung), ist in der Plugin-Referenz beschrieben. Für reines Weiterleiten ohne Umschreiben werden keine Actions benötigt – dann genügt destination.

Eine Action läuft nur, wenn eine Regel sie nennt. Es gibt kein automatisches Laden: Wird ein Action-Name von keiner Filter-Regel unter actions referenziert, passiert nichts – die Definition allein genügt nicht. Der Router weist beim Start und bei jedem reload mit einer Warnung im Log darauf hin und nennt den Namen. Zum Aktivieren also immer beides tun: die Action definieren und sie in der passenden Regel eintragen.

3.5 management und audit

Diese beiden Abschnitte sind meist mit den Standardwerten korrekt und müssen selten angefasst werden.

management steuert die beiden Zusatz-Ports für Betrieb und Überwachung:

management:
  enabled: true
  agent-check-port: 8404            # Zustandsabfrage durch HAProxy (netzwerkerreichbar)
  agent-check-bind-address: "0.0.0.0"
  management-port: 8405             # Operator-Kommandos (nur lokal, 127.0.0.1)
  management-bind-address: "127.0.0.1"

Der Management-Port ist bewusst nur lokal (127.0.0.1) erreichbar. Er ist ohne zusätzliche Absicherung nicht authentifiziert – d.h. jeder lokale Prozess könnte den Router stoppen. Optional lässt sich über eine Token-Datei ein Passwort verlangen; Details dazu in der Management-Doku.

audit schreibt einen Prüf-/Nachweispfad in die Datenbank: je eine Zeile pro Verbindung und pro DICOM-Operation, verlustfrei über einen lokalen Zwischenspeicher (einzige Ausnahme: läuft der Zwischenspeicher über sein spool-max-mb-Limit, etwa bei einem sehr langen Datenbank-Ausfall, werden weitere Einträge verworfen und gezählt). Ein Datenbank-Ausfall führt nicht zu Datenverlust und beeinträchtigt das Routing nicht.

audit:
  enabled: true
  spool-path: /var/imconnect/audit-spool
  spool-max-mb: 512
  batch-size: 100
  flush-interval: 1s
  capture-patient-name: false        # Patientenname (PHI!) – standardmässig aus

capture-patient-name sollte nur aus gutem Grund aktiviert werden, da es Patientendaten (PHI) speichert.

3.6 Welche Änderungen ein reload übernimmt

Viele Änderungen greifen ohne Neustart über das Kommando reload (siehe Abschnitt 4): Neue Verbindungen nutzen dann die neue Konfiguration, bereits laufende Verbindungen behalten ihre bisherige. Einige grundlegende Felder werden dagegen erst beim Start gebunden und verlangen einen Neustart.

Sofort per reload übernehmbar Nur mit Neustart wirksam
filter (Routing-Regeln) router.listener.host / port / aet
destinations (forward-/reject-/qr-bridge-Ziele) – Ausnahme: store: einer qr-bridge-Destination der gesamte management-Block
actions (Plugins und deren config:) – Ausnahme: store: der CMoveTrackingStage der gesamte audit-Block – Ausnahme: capture-patient-name
proxyprotocol store: einer CMoveTrackingStage-Action
router.listener.socketTimeout / connectTimeout / tcplog store: einer qr-bridge-Destination
audit.capture-patient-name
die router-Speicher-Schwellen

Bei den beiden store:-Einträgen gilt das nicht nur für den Wert: Verschwindet ein solcher Eintrag (entfernt oder umbenannt) und rückt im selben reload kein Eintrag mit demselben Store nach, ist der reload ebenfalls neustartpflichtig. Die Speicher-Instanzen werden beim Start angelegt, und ein umbenannter Eintrag ist von einem entfernten nicht zu unterscheiden. Reine Ergänzungen bleiben übernehmbar.

Versucht ein reload ein neustartpflichtiges Feld zu ändern, wird er mit requires restart abgelehnt und die bisherige Konfiguration bleibt aktiv. Die vollständige Feldliste steht in der Management-Doku.


4. Betrieb

Gesteuert wird der laufende Router über das mitgelieferte CLI-Werkzeug dicom-router-ctl (unter /opt/imconnect/bin/).

Kommando Wirkung
dicom-router-ctl status Zustand, Anzahl aktiver Verbindungen, Laufzeit, Audit-Status.
dicom-router-ctl connections Liste der aktiven DICOM-Verbindungen.
dicom-router-ctl drain Keine neuen Verbindungen mehr annehmen; bestehende laufen weiter.
dicom-router-ctl resume Nach einem drain wieder Verbindungen annehmen.
dicom-router-ctl reload Konfiguration neu einlesen (siehe Abschnitt 3.6).
dicom-router-ctl shutdown Sofortiger geordneter Stopp.
dicom-router-ctl drain-and-shutdown Erst leeren, auf 0 Verbindungen warten, dann stoppen.

Stopp: shutdown vs. drain-and-shutdown

Der Unterschied ist wichtig:

  • shutdown stoppt den Router sofort. Noch laufende Übertragungen werden dabei getrennt.
  • drain-and-shutdown nimmt zuerst keine neuen Verbindungen mehr an, wartet, bis alle laufenden Übertragungen fertig sind, und stoppt erst danach. Das ist der schonende Weg für ein Wartungsfenster oder Update.

Neustart über systemd

Ein Neustart erfolgt über systemd:

systemctl restart dicom-router

Das ist automatisch schonend: Beim Stoppen ruft die systemd-Unit intern drain-and-shutdown auf, wartet also erst alle laufenden Verbindungen ab, bevor sie den Prozess beendet. Für die Wartezeit ist ein grosszügiges Zeitlimit gesetzt. Nach einem unerwarteten Absturz startet der Dienst automatisch neu; ein sauberer Stopp bleibt gestoppt.

Konfiguration ändern (Workflow)

  1. /opt/imconnect/config/dicom-router.yaml bearbeiten.
  2. Optional vorab prüfen, ganz ohne den laufenden Router anzufassen:
    cd /opt/imconnect
    java -jar dicom-router-core-*.jar --validate-config config/dicom-router.yaml
    
    Meldet OK oder listet die Fehler auf (gleiche Prüfung wie beim Reload). Dazu zählt jeder Schlüssel, den es nicht gibt: ein Tippfehler oder eine falsche Schreibweise (tokenFile statt token-file) lässt die Prüfung mit Abschnitt und Feldnamen fehlschlagen, statt die Einstellung stillschweigend zu verwerfen. Alle Fehler einer Datei werden in einem Durchgang gemeldet. Wichtig: den Befehl im Installationsverzeichnis ausführen (bzw. in Automatisierung wie Ansible chdir setzen) — Plugin-Klassen aus plugins/ werden relativ zum Arbeitsverzeichnis gesucht; aus einem anderen Verzeichnis meldet die Prüfung sonst fälschlich Fehler, wenn die Konfiguration externe Plugins verwendet.
  3. Übernehmen: dicom-router-ctl reload.
  4. Antwort prüfen:
  5. OK config reloaded – Änderung ist aktiv.
  6. ERROR ... requires restart – ein neustartpflichtiges Feld wurde geändert; dann stattdessen systemctl restart dicom-router.
  7. Andere ERROR ... – die Konfiguration hat einen Fehler; die alte Konfiguration bleibt aktiv, bis der Fehler behoben und ein erneuter reload erfolgreich ist.

Mehrere Hosts: Konfiguration verteilen (dicom-router-sync)

Läuft der Router auf mehreren Hosts (z. B. hinter HAProxy), wird die Konfiguration auf einem Host bearbeitet und mit dicom-router-sync auf alle anderen verteilt:

  1. Die anderen Hosts einmalig in /opt/imconnect/config/cluster-hosts eintragen (Vorlage: share/cluster-hosts.example; eine Zeile pro Host, user@host erlaubt). Voraussetzung ist passwortloser ssh-Zugang vom Bearbeitungs-Host zu jedem Ziel.
  2. Nach dem Bearbeiten der YAML: dicom-router-sync ausführen. Das Tool
  3. sichert den Stand automatisch in config/config-history/ (Historie für Rückfragen und Rollback — einfach einen alten Stand zurückkopieren und erneut verteilen),
  4. reloaded zuerst den lokalen Router als Probe: Wird die Konfiguration abgelehnt, bricht das Tool ab, bevor irgendein anderer Host angefasst wird — überall bleibt die alte Konfiguration aktiv,
  5. kopiert die Datei dann auf alle Hosts, reloaded sie und prüft per Checksumme, dass überall derselbe Stand liegt — wo der Router es meldet, zusätzlich gegen den config-hash aus status, also den tatsächlich geladenen Stand (Ergebnis-Tabelle am Ende, MATCH (loaded)).
  6. Meldet die Probe requires restart, verteilt das Tool die Datei, überspringt die Reloads und zeigt die Anleitung für den rollierenden Neustart (systemctl restart dicom-router, Host für Host).

Weitere Modi: dicom-router-sync --check vergleicht nur die Stände aller Hosts (nützlich als Drift-Kontrolle, ändert nichts); --no-reload verteilt nur.

Die Ergebnis-Tabelle unterscheidet dabei, woran es lag: UNREACHABLE heisst, der Host war per ssh nicht erreichbar, MISSING dagegen, dass die ssh-Verbindung stand, auf dem Host aber (noch) keine Konfigurationsdatei liegt — typisch für einen frisch aufgesetzten Knoten. NO-SHA256SUM meldet einen Host ohne sha256sum-Kommando, auf dem sich der Stand nicht prüfen lässt.

Neue Destination mit automatischem Return-Port (return-port: 0)

Wer HAProxy-Rückwege nutzt (haproxy-return mit konfigurierter port-range), muss Return-Ports nicht mehr von Hand vergeben:

  1. Die neue Destination mit dem Marker return-port: 0 anlegen:
    destinations:
      ct_neu:
        host: 192.168.10.7
        port: 104
        aet: CT_NEU
        return-port: 0        # Port wird automatisch vergeben
    
  2. Den Marker lokal ersetzen lassen:
    cd /opt/imconnect
    java -jar dicom-router-core-*.jar --allocate-return-ports config/dicom-router.yaml
    
    Das Subkommando vergibt den kleinsten freien Port der port-range und zeigt die Zuordnung an (z. B. ct_neu: return-port 5014); bereits vergebene Ports anderer Destinations bleiben immer unverändert. (Vor Schritt 3 nötig — dicom-router-haproxy-sync bricht auf einem unallozierten Marker ab.)
  3. Den neuen Rückweg auf den HAProxy-Hosts aktivieren: dicom-router-haproxy-sync (nächster Abschnitt).
  4. Erst jetzt die Router-Konfiguration verteilen: dicom-router-sync. In dieser Reihenfolge wählt der Router nie einen Port an, den HAProxy noch nicht kennt. Ohne Cluster (Einzel-Host) stattdessen dicom-router-ctl reload.

Ein vergessener Marker kann nie unbemerkt live gehen: Start, reload und --validate-config lehnen eine Konfiguration mit return-port: 0 mit einer klaren Fehlermeldung ab.

Rückwege, die von Hand in der HAProxy-Config stehen: Die generierte HAProxy-Datei enthält nur Destinations, deren return-port innerhalb der port-range liegt. Wer einen Rückweg selbst in seiner HAProxy-Konfiguration führt (fester Port, abweichendes Backend-Setup, Alt-Eintrag), vergibt der Destination einen return-port ausserhalb der Range. Der Router wählt sie weiterhin über vip:return-port an, der Rückweg taucht aber nicht in der generierten Datei auf — sonst gäbe es zwei listen-Blöcke auf demselben bind und HAProxy startet nicht mehr. Übersprungene Destinations listet die generierte Datei als Kommentar auf:

# skipped (return-port outside port-range 5000-5999 — manual HAProxy entry):
#   ct_b  bind 10.0.0.10:4711  -> 192.168.10.9:104

Beim Update auf diese Version gilt das rückwirkend: Wer eine port-range konfiguriert hat und Destinations mit einem return-port ausserhalb davon führt, verliert deren listen-Blöcke beim nächsten dicom-router-haproxy-sync aus der generierten Datei. dicom-router-haproxy-sync --check zeigt das vorab an und benennt die Blöcke ausdrücklich als weiterhin konfiguriert („target has listen block(s) for destination(s) STILL in the YAML … do NOT remove them") — sie gehören vorher in die eigene HAProxy-Konfiguration übernommen.

HAProxy-Rückwege verteilen (dicom-router-haproxy-sync)

Wer HAProxy-Rückwege nutzt, muss die generierte HAProxy-Datei nicht mehr von Hand auf die HAProxy-Hosts bringen:

  1. Die HAProxy-Hosts einmalig in /opt/imconnect/config/haproxy-hosts eintragen (Vorlage: share/haproxy-hosts.example; eine Zeile pro Host, optional mit Zielpfad, Standard /etc/haproxy/conf.d/dicom-return.cfg). Voraussetzungen: passwortloser ssh-Zugang, Schreibrecht auf den Zielpfad und eine sudo-Regel für systemctl reload haproxy (Details: Deployment-Doku).
  2. Nach jeder Änderung an den Rückwegen: dicom-router-haproxy-sync ausführen. Das Tool generiert die Datei aus der YAML, kopiert sie auf alle Hosts (auf unveränderten Hosts entfallen Kopie und Prüfung, der Reload läuft trotzdem — so holt ein Wiederholungslauf einen zuvor gescheiterten Reload nach), prüft sie dort mit haproxy -c, bevor sie aktiv wird (bei einem Fehler bleibt auf dem Host der alte Stand unangetastet), reloaded HAProxy unterbrechungsfrei (laufende Übertragungen überleben) und prüft per Checksumme, dass überall derselbe Stand liegt (Ergebnis-Tabelle am Ende). Es verwaltet nur diese eine Datei — nie die Haupt-Konfiguration von HAProxy.

Reihenfolge-Regel:

  • Destination hinzugefügt → erst dicom-router-haproxy-sync, dann dicom-router-sync — ein neuer listen-Block ist harmlos, solange ihn niemand anwählt; umgekehrt würde der Router einen Port anwählen, den HAProxy noch nicht kennt.
  • Destination entfernt → umgekehrt: erst dicom-router-sync, dann dicom-router-haproxy-sync.
  • return-port aus der port-range heraus geändert (der Rückweg wird von Hand geführt, siehe Abschnitt oben) → erst den listen-Block in die eigene HAProxy-Konfiguration übernehmen, dann dicom-router-haproxy-sync — sonst ist der Rückweg dazwischen nicht erreichbar.

Alle drei Regeln gibt das Tool auch selbst als Hinweis aus — auch die dritte: auf dem HAProxy-Host sieht sie aus wie eine entfernte Destination, das Skript erkennt sie aber an der Skip-Liste der generierten Datei und meldet dann ausdrücklich, dass der Block nicht verwaist ist und nicht entfernt werden darf. Der Abgleich läuft über den Blocknamen: wird eine solche Destination zusätzlich umbenannt, meldet das Skript den alten Block als verwaist, obwohl sein Rückweg weiter benutzt wird. Vor dem Entfernen eines gemeldeten Blocks deshalb die Skip-Liste der generierten Datei prüfen — der Hinweistext sagt das auch selbst.

Weitere Modi: dicom-router-haproxy-sync --check vergleicht nur die Stände aller Hosts (ändert nichts); --no-reload verteilt und prüft, reloaded aber nicht.


5. Fehlersuche

Wo liegen die Logs?

Es gibt zwei unabhängige Log-Mechanismen – leicht zu verwechseln:

Was Wo / wie Inhalt
Anwendungslog Datei dicom-router.log im Log-Verzeichnis (Standard /var/log/imconnect), konfiguriert über logback.xml normale Meldungen (INFO/WARN/ERROR) – die erste Anlaufstelle
Binäre PDU-Mitschnitte Verzeichnis aus router.log-folder, aktiviert über logLevel 5/6/7 in einer Filter-Regel vollständige DICOM-Pakete als Binärdateien – nur zur Diagnose

Das normale Anwendungslog wird automatisch rotiert. Die PDU-Mitschnitte sind ein Diagnose-Werkzeug für schwierige Fälle: Sie enthalten vollständige Patientendaten (PHI), werden nicht rotiert oder automatisch gelöscht und sollten nur vorübergehend aktiviert werden. Nach der Analyse den logLevel zurücksetzen und die Mitschnitte entfernen.

Das Setzen von logLevel in der Filter-Regel genügt — die Mitschnitt-Funktion ist fest eingebaut, es ist keine Action nötig. logLevel ist hot-reloadbar: Wert ändern, dicom-router-ctl reload, fertig — und zum Abschalten genauso zurück auf 0. (Das Plugin PduLoggingStage in der Plugin-Referenz fügt lediglich zusätzliche benannte Logger hinzu.)

logLevel-Stufen in einer Filter-Regel:

Wert Bedeutung
0 keine Protokollierung
1 eingehend protokollieren
2 ausgehend protokollieren
3 ein- und ausgehend protokollieren
5 eingehend als Datei mitschneiden
6 ausgehend als Datei mitschneiden
7 ein- und ausgehend als Datei mitschneiden

Laufenden Zustand ansehen

dicom-router-ctl status        # Zustand, Verbindungszahl, Laufzeit, Audit
dicom-router-ctl connections   # welche Verbindungen gerade offen sind

Typische Fehlerbilder

  • Eine Verbindung wird abgelehnt. Zuerst dicom-router-ctl status prüfen: Steht der Router im Zustand drain, nimmt er absichtlich keine neuen Verbindungen an (dicom-router-ctl resume behebt das). Ist der Zustand ready, greift wahrscheinlich eine reject-Destination – meist die Auffang-Regel am Ende der Filter-Liste (deren message erscheint im Anwendungslog). Prüfen: Gibt es eine passende Regel vor der Auffang-Regel? Stimmen callingAET/calledAET exakt (Gross-/Kleinschreibung, keine Leerzeichen)? Ist der richtige port gemeint?

  • Verbindungen werden abgebrochen, seit eine Action dazukam. Lässt sich ein in einer Regel eingetragenes Plugin nicht laden oder nicht mit seiner Konfiguration starten, bricht der Router die betroffene Association ab, statt sie ohne diesen Verarbeitungsschritt weiterzuleiten – eine Verbindung ohne ihre konfigurierte Anonymisierung oder Umschreibung soll nicht unbemerkt laufen. Im Anwendungslog steht eine ERROR-Zeile mit Plugin und Action. Vorab findet man das mit --validate-config (siehe Abschnitt 4): die Prüfung lädt jede eingetragene Plugin-Klasse und jeden config:-Block.

  • Ziel nicht erreichbar / Weiterleitung schlägt fehl. host, port und aet der forward-Destination gegen das Ziel-System prüfen. Ist das Ziel-System über das Netz erreichbar und akzeptiert es den vom Router genutzten AE-Titel?

  • reload wird abgelehnt.

  • requires restart: Es wurde ein neustartpflichtiges Feld geändert (Listener, management, audit oder ein store: — auch durch Entfernen bzw. Umbenennen eines Tracking-Eintrags oder einer qr-bridge-Destination, siehe Abschnitt 3.6) → systemctl restart dicom-router.
  • anderer ERROR: Die YAML enthält einen Fehler (Tippfehler, ungültiger regulärer Ausdruck, unbekanntes Ziel) oder die Datei fehlt (dann wird nichts neu angelegt). Der Router läuft mit der alten Konfiguration weiter, bis ein reload fehlerfrei durchläuft.