Verwendung von TSDuck zur Überwachung von IP(TS)-Streams

Aktuell gibt es fertige (proprietäre) Lösungen zur Überwachung von IP(TS)-Strömen, zum Beispiel VB und iQ, sie verfügen über einen recht umfangreichen Funktionsumfang und solche Lösungen sind in der Regel bei großen Betreibern zu finden, die mit TV-Diensten arbeiten. In diesem Artikel wird eine Lösung auf Basis des Open-Source-Projekts TSDuck, beschrieben, die für die minimale Überwachung von IP(TS)-Strömen über den CC (continuity counter) und die Bitrate vorgesehen ist. Eine mögliche Anwendung ist die Überwachung des Paket- oder Gesamtstromverlusts über einen gemieteten L2-Kanal (der nicht ordnungsgemäß überwacht werden kann, zum Beispiel durch das Auslesen der Verlustzähler in Warteschlangen).

Kurze Einführung in TSDuck

TSDuck ist eine Open-Source-Software (lizenziert unter 2-Clause BSD), ein Set von Konsolenwerkzeugen und einer Bibliothek zur Entwicklung eigener Werkzeuge oder Plugins für die Manipulation von TS-Strömen. Als Eingabe kann es mit IP (Multicast/Unicast), HTTP, HLS, DVB-Tunern, Dektec DVB-ASI-Demodulatoren arbeiten, es gibt einen internen TS-Stream-Generator und die Möglichkeit, aus Dateien zu lesen. Als Ausgabe kann in eine Datei, IP (Multicast/Unicast), HLS, Dektec DVB-ASI und HiDes-Modulatoren, Player (mplayer, vlc, xine) und Drop geschrieben werden. Zwischen Eingabe und Ausgabe können verschiedene Traffic-Prozessoren integriert werden, wie PID-Mapping, Scrambling/Descrambling, Analyse von CC-Zählern, Berechnung der Bitrate und andere gängige Operationen für TS-Ströme.

In diesem Artikel werden IP-Ströme (Multicast) als Eingabe verwendet, die Prozessoren bitrate_monitor (aus dem Namen ersichtlich, worum es sich handelt) und continuity (Analyse der CC-Zähler) umfassen. IP-Multicast kann ohne größere Probleme durch einen anderen von TSDuck unterstützten Eingangstyp ersetzt werden.

Es gibt offizielle Builds/Pakete von TSDuck für die meisten gängigen Betriebssysteme. Für Debian gibt es diese nicht, aber es war problemlos möglich, sie für Debian 8 und Debian 10 zu erstellen.

Es wird die Version TSDuck 3.19-1520 verwendet, als Betriebssystem kommt Linux zum Einsatz (für die Vorbereitung der Lösung wurde Debian 10 verwendet, für die tatsächliche Nutzung – CentOS 7)

Vorbereitung von TSDuck und des Betriebssystems

Bevor Sie reale Datenströme überwachen, sollten Sie sicherstellen, dass TSDuck korrekt funktioniert und keine Dropouts auf der Ebene der Netzwerkkarte oder des Betriebssystems (Sockets) vorliegen. Dies ist notwendig, um später nicht zu raten, wo die Dropouts aufgetreten sind – im Netzwerk oder „innerhalb des Servers“. Dropouts auf der Ebene der Netzwerkkarte können mit dem Befehl ethtool -S ethX überprüft werden, das Tuning erfolgt ebenfalls mit ethtool (in der Regel muss der RX-Puffer (-G) erhöht und manchmal müssen bestimmte Offloads (-K) deaktiviert werden). Als allgemeine Empfehlung kann man raten, einen separaten Port für den Empfang des analysierten Traffics zu verwenden, wenn dies möglich ist, um falsche Alarme zu minimieren, die dadurch entstehen, dass ein Drop spezifisch auf dem Port des Analysators aufgrund anderer Traffic-Quellen aufgetreten ist. Wenn diese Möglichkeit nicht besteht (verwendet wird ein Minicomputer/NUC mit einem Port), ist es sehr ratsam, die Priorisierung des analysierten Traffics im Verhältnis zu dem anderen auf dem Gerät, an das der Analysator angeschlossen wird, einzustellen. In Bezug auf virtuelle Umgebungen sollte man hier vorsichtig sein und in der Lage sein, Dropouts von Paketen vom physischen Port bis zur Anwendung innerhalb der virtuellen Maschine zu finden.

Generierung und Empfang des Streams innerhalb des Hosts

Als ersten Schritt zur Vorbereitung von TSDuck werden wir Traffics innerhalb eines Hosts mit netns generieren und empfangen.

Umgebung vorbereiten:

ip netns add P #erstellen Sie netns P, in dem die Traffic-Analyse stattfinden wird
ip link add type veth #erstellen Sie ein veth-Paar – veth0 bleibt im Standard-netns (in dieses Interface wird Traffic generiert)
ip link set dev veth1 netns P #veth1 – in netns P verschieben (auf diesem Interface wird der Traffic empfangen)
ip netns exec P ifconfig veth1 192.0.2.1/30 up #IP auf veth1 aktivieren, welcher genau ist, spielt keine Rolle
ip netns exec P ip ro add default via 192.0.2.2 #konfigurieren Sie die Standardroute innerhalb von netns P
sysctl net.ipv6.conf.veth0.disable_ipv6=1 #deaktivieren Sie IPv6 auf veth0 – dies geschieht, damit im TX-Zähler kein fremder Müll landet
ifconfig veth0 up #aktivieren Sie das Interface veth0
ip route add 239.0.0.1 dev veth0 #erstellen Sie eine Route, damit das Betriebssystem den Traffic zu 239.0.0.1 Richtung veth0 leitet

Die Umgebung ist bereit. Wir starten den Traffic-Analysator:

ip netns exec P tsp --realtime -t 
 -I ip 239.0.0.1:1234 
 -P continuity 
 -P bitrate_monitor -p 1 -t 1 
 -O drop

wobei „-p 1 -t 1“ bedeutet, dass die Bitrate jede Sekunde berechnet und die Informationen über die Bitrate jede Sekunde ausgegeben werden sollen
Wir starten den Traffic-Generator mit einer Geschwindigkeit von 10 Mbit/s:

tsp -I craft 
 -P regeln -b 10000000 
 -O ip -p 7 -e --local-port 6000 239.0.0.1:1234

wobei «-p 7 -e» bedeutet, dass 7 TS-Pakete in 1 IP-Paket verpackt werden sollen und dies strikt (-e), d.h. es muss immer auf 7 TS-Pakete vom letzten Prozessor gewartet werden, bevor das IP-Paket gebildet wird.

Der Analysator beginnt, die erwarteten Nachrichten auszugeben:

* 2020/01/03 14:55:44 - bitrate_monitor: 2020/01/03 14:55:44, TS Bitrate: 9.970.016 bits/s
* 2020/01/03 14:55:45 - bitrate_monitor: 2020/01/03 14:55:45, TS Bitrate: 10.022.656 bits/s
* 2020/01/03 14:55:46 - bitrate_monitor: 2020/01/03 14:55:46, TS Bitrate: 9.980.544 bits/s

Jetzt fügen wir ein wenig Drops hinzu:

ip netns exec P iptables -I INPUT -d 239.0.0.1 -m statistic --mode random --probability 0.001 -j DROP

und es erscheinen Nachrichten wie diese:

* 2020/01/03 14:57:11 - continuity: Paketindex: 80.745, PID: 0x0000, 7 Pakete fehlen
* 2020/01/03 14:57:11 - continuity: Paketindex: 83.342, PID: 0x0000, 7 Pakete fehlen 

was zu erwarten ist. Wir schalten den Paketverlust aus (ip netns exec P iptables -F) und versuchen, die Bitrate des Generators auf 100 Mbit/s zu erhöhen. Der Analysator berichtet viele CC-Fehler und etwa 75 Mbit/s statt 100. Wir versuchen herauszufinden, wer schuld ist – ob der Generator nicht schnell genug ist oder ob das Problem nicht bei ihm liegt. Dafür starten wir die Generierung einer festen Anzahl von Paketen (700000 TS-Pakete = 100000 IP-Pakete):

# ifconfig veth0 | grep TX
       TX packets 151825460  bytes 205725459268 (191.5 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# tsp -I craft -c 700000 -P regulate -b 100000000 -P count -O ip -p 7 -e --local-port 6000 239.0.0.1:1234
* count: PID    0 (0x0000):    700,000 packets
# ifconfig veth0 | grep TX
        TX packets 151925460  bytes 205861259268 (191.7 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Wie zu sehen ist, wurden genau 100000 IP-Pakete generiert (151925460-151825460). Das bedeutet, wir schauen uns an, was mit dem Analysator passiert. Dazu vergleichen wir mit dem RX-Zähler auf veth1, der genau dem TX-Zähler auf veth0 entspricht, und dann schauen wir uns an, was auf Socket-Ebene passiert:

# ip netns exec P cat /proc/net/udp                                                                                                           
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops             
  133: 010000EF:04D2 00000000:0000 07 00000000:00000000 00:00000000 00000000     0        0 72338 2 00000000e0a441df 24355 

Hier sehen wir die Anzahl der Drops = 24355. In TS-Paketen sind das 170485 oder 24,36% von 700000, somit sehen wir, dass die besagten 25% des verlorenen Bitrates Drops im UDP-Socket sind. Drops im UDP-Socket treten normalerweise aufgrund von Pufferengpässen auf, wir schauen uns an, wie groß die Standardgröße des Sockets und die maximale Größe des Sockets sind:

# sysctl net.core.rmem_default
net.core.rmem_default = 212992
# sysctl net.core.rmem_max
net.core.rmem_max = 212992

Wenn Anwendungen die Puffermenge nicht ausdrücklich anfordern, werden Sockets mit einer Größe von 208 KB erstellt, aber wenn sie mehr anfordern, erhalten sie dennoch nicht die gewünschte Menge. Da in tsp für den IP-Eingang die Puffermenge festgelegt werden kann (—buffer-size), werden wir die Standardgröße des Sockets nicht ändern, sondern nur die maximale Größe des Sockets festlegen und die Puffermenge ausdrücklich über die Argumente von tsp angeben:

sysctl net.core.rmem_max=8388608
ip netns exec P tsp --realtime -t -I ip 239.0.0.1:1234 -b 8388608 -P continuity -P bitrate_monitor -p 1 -t 1 -O drop

Mit dieser Optimierung des Socket-Puffers beträgt die jetzt gemeldete Bitrate etwa 100 Mbit/s, CC-Fehler gibt es keine.

Zum CPU-Verbrauch selbst der Anwendung tsp. Bei einem einzelnen Kern i5-4260U CPU @ 1,40 GHz benötigt die Analyse eines 10 Mbit/s Streams etwa 3-4% CPU, 100 Mbit/s — 25%, 200 Mbit/s — 46%. Bei festgelegtem % Paketverlust steigt die CPU-Auslastung praktisch nicht an (kann aber sinken).

Auf leistungsfähigerer Hardware konnte problemlos einflussreiche Streams über 1 Gbit/s generiert und analysiert werden.

Testen mit realen Netzwerkkarten

Nach dem Testen mit einem veth-Paar müssen zwei Hosts oder zwei Ports eines Hosts verbunden werden. Es wird ein Generator auf einem gestartet und ein Analyzer auf dem anderen. Überraschungen blieben hier aus, aber tatsächlich hängt alles von der Hardware ab; je schwächer, desto interessanter wird es hier.

Verwendung der erhaltenen Daten durch das Monitoring-System (Zabbix)

Tsp hat keine maschinenlesbare API wie SNMP oder Ähnliches. CC-Nachrichten müssen mindestens im Sekundentakt aggregiert werden (bei hohem Paketverlust können es Hunderte/Thousand/Dutzende Tausende pro Sekunde sein, abhängig von der Bitrate).

Sofern man Informationen speichern und Grafiken zu CC-Fehlern und Bitrate zeichnen möchte, könnten folgende Möglichkeiten entstehen:

  1. Den Output von tsp parsen und aggregieren (nach CC), d.h. ihn in die gewünschte Form umwandeln.
  2. Tsp selbst und/oder die Prozessoreinheiten bitrate_monitor und continuity so anpassen, dass das Ergebnis in einer maschinenlesbaren Form bereitgestellt wird, die für das Monitoring-System geeignet ist.
  3. Eine eigene Anwendung über der tsduck-Bibliothek schreiben.

Offensichtlich ist die erste Variante in Bezug auf den Arbeitsaufwand die einfachste, insbesondere da die tsduck selbst in einer niedrigstufigen (nach modernen Standards) Sprache (C++) geschrieben ist.

Ein einfacher Prototyp eines Parsers+Aggregators in bash zeigte, dass bei einem 10 Mbit/s Stream und 50% Paketverlust (schlimmste Variante) der bash-Prozess 3-4 mal mehr CPU verbrauchte als der Prozess tsp selbst. Eine solche Entwicklung ist inakzeptabel. Der Code dieses Prototyps ist unten.

Nudeln in bash

#!/usr/bin/env bash

missingPackets=0
ccErrorSeconds=0
regexMissPackets='^* (.+) - continuity:.*missing ([0-9]+) packets$'
missingPacketsTime=""

ip netns exec P tsp --realtime -t -I ip -b 8388608 "239.0.0.1:1234" -O drop -P bitrate_monitor -p 1 -t 1  -P continuity 2>&1 | 
while read i
do
    #line example:* 2019/12/28 23:41:14 - continuity: packet index: 6,078, PID: 0x0100, missing 5 packets
    #line example 2: * 2019/12/28 23:55:11 - bitrate_monitor: 2019/12/28 23:55:11, TS bitrate: 4,272,864 bits/s
    if [[ "$i" == *continuity:* ]] 
    then
        if [[ "$i" =~ $regexMissPackets ]]
        then
            missingPacketsTimeNew="${BASH_REMATCH[1]}" #timestamp (seconds)
            if [[ "$missingPacketsTime" != "$missingPacketsTimeNew" ]] #new second with CC error
            then
                ((ccErrorSeconds += 1))
            fi
            missingPacketsTime=$missingPacketsTimeNew
            packets=${BASH_REMATCH[2]} #TS missing packets
            ((missingPackets += packets))
        fi
    elif [[ "$i" == *bitrate_monitor:* ]]
    then
        : #...
    fi
done

Abgesehen davon, dass es unzulässig langsam arbeitet, fehlen in Bash normale Threads, Bash-Jobs sind eigenständige Prozesse, und es war notwendig, den Wert von missingPackets einmal pro Sekunde im Side-Effekt zu protokollieren (bei Erhalt der Bitrate-Nachricht, die jede Sekunde eintrifft). Letztendlich wurde Bash in Ruhe gelassen und beschlossen, eine Wrapper (Parser + Aggregator) in Golang zu schreiben. Der CPU-Verbrauch des vergleichbaren Codes in Golang liegt 4-5 Mal niedriger als der des Prozesses tsp. Die Beschleunigung der Wrapper durch den Austausch von Bash gegen Golang betrug etwa das 16-fache, und insgesamt ist das Ergebnis akzeptabel (CPU-Overhead von 25 % im schlimmsten Fall). Die Quell-Datei in Golang befindet sich hier.

Start der Wrapper

Für den Start der Wrapper wurde eine einfache Service-Vorlage für systemd erstellt (hier). Es wird vorausgesetzt, dass die Wrapper selbst in eine Binärdatei (go build tsduck-stat.go) kompiliert wurde, die unter /opt/tsduck-stat/ abgelegt ist. Es wird vorausgesetzt, dass Golang mit Unterstützung für monotone Uhren (>=1.9) verwendet wird.

Um eine Service-Instanz zu erstellen, muss der Befehl systemctl enable tsduck-stat@239.0.0.1:1234 ausgeführt werden, gefolgt von systemctl start tsduck-stat@239.0.0.1:1234.

Discovery aus Zabbix

Damit Zabbix die Entdeckung laufender Dienste durchführen kann, wurde ein Gruppennamen-Generator (discovery.sh) im für Zabbix-Discovery erforderlichen Format erstellt, der dort platziert wurde — in /opt/tsduck-stat. Um die Discovery über zabbix-agent zu starten, muss man eine .conf-Datei in das Verzeichnis mit den Konfigurationen des Zabbix-Agenten einfügen, um einen Benutzerparameter hinzuzufügen.

Zabbix-Vorlage

Die erstellte Vorlage (tsduck_stat_template.xml) enthält eine Regel zur automatischen Erkennung, Prototypen von Datenpunkten, Grafiken und Triggern.

Kurze Checkliste (falls jemand sie nutzen möchte)

  1. Stellen Sie sicher, dass tsp bei "idealen" Bedingungen keine Pakete verliert (Generator und Analyzer sind direkt verbunden). Bei Paketverlusten siehe Punkt 2 oder den Artikeltext dazu.
  2. Optimieren Sie das maximale Socket-Puffer (net.core.rmem_max=8388608).
  3. Kompilieren Sie tsduck-stat.go (go build tsduck-stat.go).
  4. Legen Sie die Service-Vorlage in /lib/systemd/system ab.
  5. Starten Sie die Dienste mit systemctl und überprüfen Sie, ob die Zähler (grep "" /dev/shm/tsduck-stat/*) zu erscheinen beginnen. Die Anzahl der Dienste entspricht der Anzahl der Multicast-Streams. Möglicherweise müssen Sie eine Route zur Multicast-Gruppe erstellen, eventuell rp_filter deaktivieren oder eine Route zur Quell-IP erstellen.
  6. Starten Sie discovery.sh und stellen Sie sicher, dass es JSON generiert.
  7. Legen Sie die zabbix-agent-Konfiguration ab und starten Sie den zabbix-agent neu.
  8. Laden Sie die Vorlage in Zabbix hoch, wenden Sie sie auf den Host an, auf dem die Überwachung durchgeführt wird und der Zabbix-Agent installiert ist, warten Sie etwa 5 Minuten und sehen Sie nach, ob neue Datenelemente, Grafiken und Trigger erschienen sind.

Ergebnis

Verwendung von TSDuck zur Überwachung von IP(TS)-Streams

Für die Aufgabe, Paketverluste zu identifizieren, ist das fast ausreichend; besser als gar keine Überwachung.

In der Tat können CC-'Verluste' beim Zusammenfügen von Videoabschnitten auftreten (soweit ich weiß, so werden Einfügungen in den lokalen Fernsehzentren in der RF gemacht, d.h. ohne Neuzählung des CC-Zählers), das sollte man im Hinterkopf behalten. In proprietären Lösungen wird dieses Problem teilweise durch die Erkennung von SCTE-35-Marken umgangen (wenn sie vom Stream-Generator hinzugefügt werden).

Aus der Sicht der Überwachung der Übertragungsqualität fehlt die Überwachung des Jitters (IAT), da TV-Geräte (sei es Modulatoren oder Endgeräte) Anforderungen an diesen Parameter haben und man den Jitter-Puffer nicht immer bis zur Unendlichkeit aufblasen kann. Und der Jitter kann instabil werden, wenn auf der Übertragung Ausrüstung mit großen Puffern verwendet wird und QoS für die Übertragung solcher Realtime-Traffic nicht richtig konfiguriert oder unzureichend gut eingestellt ist.

Quelle: habr.com

60GB SSD 8Gb DDR4