Verwenden von TSDuck zum Überwachen von IP(TS)-Streams

Heute gibt es beispielsweise fertige (proprietäre) Lösungen zur Überwachung von IP(TS)-Streams VB и iQSie verfügen über einen recht umfangreichen Funktionsumfang und in der Regel verfügen große Betreiber, die sich mit TV-Diensten befassen, über solche Lösungen. Dieser Artikel beschreibt eine Lösung, die auf einem Open-Source-Projekt basiert TSDuck, konzipiert für minimale Kontrolle von IP(TS)-Streams durch CC(Continuity Counter)-Zähler und Bitrate. Eine mögliche Anwendung besteht darin, den Verlust von Paketen oder des gesamten Flusses durch einen geleasten L2-Kanal zu kontrollieren (der normalerweise nicht überwacht werden kann, beispielsweise durch Auslesen von Verlustzählern in Warteschlangen).

Ganz kurz über TSDuck

TSDuck ist eine Open-Source-Software (2-Klausel-BSD-Lizenz) (eine Reihe von Konsolendienstprogrammen und eine Bibliothek zur Entwicklung benutzerdefinierter Dienstprogramme oder Plugins) zur Manipulation von TS-Streams. Als Eingabe kann es mit IP (Multicast/Unicast), http, HLS, DVB-Tuner, Dektec DVB-ASI-Demodulator arbeiten, es gibt einen internen TS-Stream-Generator und das Lesen aus Dateien. Die Ausgabe kann das Schreiben in eine Datei, IP (Multicast/Unicast), HLS, Dektec DVB-ASI und HiDes-Modulatoren, Player (Mplayer, VLC, Xine) und Drop sein. Zwischen Eingabe und Ausgabe können verschiedene Verkehrsprozessoren eingebunden werden, beispielsweise PID-Remapping, Scrambling/Descrambling, CC-Zähleranalyse, Bitratenberechnung und andere typische Vorgänge für TS-Streams.

In diesem Artikel werden IP-Streams (Multicast) als Eingabe verwendet, die Prozessoren bitrate_monitor (aus dem Namen geht hervor, was es ist) und Continuity (Analyse von CC-Zählern) werden verwendet. Sie können IP-Multicast problemlos durch einen anderen von TSDuck unterstützten Eingabetyp ersetzen.

Es gibt offizielle Builds/Pakete TSDuck für die meisten aktuellen Betriebssysteme. Sie sind für Debian nicht verfügbar, aber wir haben es problemlos geschafft, sie unter Debian 8 und Debian 10 zu erstellen.

Als nächstes wird die Version TSDuck 3.19-1520 verwendet, als Betriebssystem kommt Linux zum Einsatz (debian 10 wurde zur Vorbereitung der Lösung verwendet, CentOS 7 wurde für den realen Einsatz verwendet)

TSDuck und OS vorbereiten

Bevor Sie echte Flüsse überwachen, müssen Sie sicherstellen, dass TSDuck ordnungsgemäß funktioniert und es keine Ausfälle auf Netzwerkkarten- oder Betriebssystemebene (Socket) gibt. Dies ist erforderlich, um später nicht zu erraten, wo die Ausfälle aufgetreten sind – im Netzwerk oder „innerhalb des Servers“. Sie können Drops auf Netzwerkkartenebene mit dem Befehl ethtool -S ethX überprüfen. Die Optimierung erfolgt mit demselben Ethtool (normalerweise müssen Sie den RX-Puffer erhöhen (-G) und manchmal einige Offloads deaktivieren (-K)). Als allgemeine Empfehlung kann empfohlen werden, nach Möglichkeit einen separaten Port für den Empfang des analysierten Datenverkehrs zu verwenden. Dadurch werden Fehlalarme minimiert, die dadurch entstehen, dass der Datenverlust aufgrund des Vorhandenseins von anderem Datenverkehr genau am Analyse-Port erfolgt ist. Wenn dies nicht möglich ist (es wird ein Mini-Computer/NUC mit einem Port verwendet), ist es sehr wünschenswert, die Priorität des analysierten Datenverkehrs im Verhältnis zum Rest auf dem Gerät zu konfigurieren, an das der Analysator angeschlossen ist. In virtuellen Umgebungen müssen Sie vorsichtig sein und in der Lage sein, Paketverluste zu finden, die von einem physischen Port ausgehen und bei einer Anwendung innerhalb einer virtuellen Maschine enden.

Erzeugung und Empfang eines Streams innerhalb des Hosts

Als ersten Schritt bei der Vorbereitung von TSDuck werden wir mithilfe von Netns Datenverkehr innerhalb eines einzelnen Hosts generieren und empfangen.

Umgebung vorbereiten:

ip netns add P #создаём netns P, в нём будет происходить анализ трафика
ip link add type veth #создаём veth-пару - veth0 оставляем в netns по умолчанию (в этот интерфейс будет генерироваться трафик)
ip link set dev veth1 netns P #veth1 - помещаем в netns P (на этом интерфейсе будет приём трафика)
ip netns exec P ifconfig veth1 192.0.2.1/30 up #поднимаем IP на veth1, не имеет значения какой именно
ip netns exec P ip ro add default via 192.0.2.2 #настраиваем маршрут по умолчанию внутри nents P
sysctl net.ipv6.conf.veth0.disable_ipv6=1 #отключаем IPv6 на veth0 - это делается для того, чтобы в счётчик TX не попадал посторонний мусор
ifconfig veth0 up #поднимаем интерфейс veth0
ip route add 239.0.0.1 dev veth0 #создаём маршрут, чтобы ОС направляла трафик к 239.0.0.1 в сторону veth0

Die Umgebung ist bereit. Wir starten den Verkehrsanalysator:

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

Dabei bedeutet „-p 1 -t 1“, dass Sie die Bitrate jede Sekunde berechnen und jede Sekunde Informationen über die Bitrate anzeigen müssen
Wir starten den Traffic-Generator mit einer Geschwindigkeit von 10Mbps:

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

wobei „-p 7 -e“ bedeutet, dass Sie 7 TS-Pakete in ein IP-Paket packen und es hart (-e) machen müssen, d. h. Warten Sie immer 1 TS-Pakete vom letzten Prozessor, bevor Sie ein IP-Paket senden.

Der Analysator beginnt mit der Ausgabe der erwarteten Meldungen:

* 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

Fügen Sie nun einige Tropfen 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 Meldungen wie diese:

* 2020/01/03 14:57:11 - continuity: packet index: 80,745, PID: 0x0000, missing 7 packets
* 2020/01/03 14:57:11 - continuity: packet index: 83,342, PID: 0x0000, missing 7 packets 

was erwartet wird. Deaktivieren Sie den Paketverlust (ip netns exec P iptables -F) und versuchen Sie, die Generatorbitrate auf 100 Mbit/s zu erhöhen. Der Analysator meldet eine Reihe von CC-Fehlern und etwa 75 Mbit/s statt 100. Wir versuchen herauszufinden, wer schuld ist – der Generator hat keine Zeit oder das Problem liegt nicht darin, dafür beginnen wir mit der Generierung einer festen Anzahl von Pakete (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 Sie sehen, wurden genau 100000 IP-Pakete generiert (151925460-151825460). Lassen Sie uns also herausfinden, was mit dem Analysator passiert. Dazu überprüfen wir mit dem RX-Zähler auf veth1, ob er genau mit dem TX-Zähler auf veth0 übereinstimmt, und schauen uns dann 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 Sie die Anzahl der Drops = 24355. In TS-Paketen beträgt dies 170485 oder 24.36 % von 700000, wir sehen also, dass dieselben 25 % der verlorenen Bitrate Drops im UDP-Socket sind. Ausfälle in einem UDP-Socket treten normalerweise aufgrund eines Puffermangels auf. Sehen Sie sich die Standard-Socket-Puffergröße und die maximale Socket-Puffergröße an:

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

Wenn Anwendungen also nicht explizit eine Puffergröße anfordern, werden Sockets mit einem Puffer von 208 KB erstellt. Wenn sie jedoch mehr anfordern, erhalten sie trotzdem nicht die angeforderte Größe. Da Sie die Puffergröße in tsp für die IP-Eingabe festlegen können (-buffer-size), werden wir die Standard-Socket-Größe nicht berühren, sondern nur die maximale Socket-Puffergröße festlegen und die Puffergröße explizit über die tsp-Argumente 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 Abstimmung des Socket-Puffers beträgt die gemeldete Bitrate nun etwa 100 Mbit/s, es treten keine CC-Fehler auf.

Entsprechend dem CPU-Verbrauch der TSP-Anwendung selbst. Bezogen auf eine i5-4260U-Kern-CPU mit 1.40 GHz erfordert die 10-Mbit/s-Flussanalyse 3–4 % CPU, 100 Mbit/s – 25 %, 200 Mbit/s – 46 %. Bei der Einstellung „% Paketverlust“ erhöht sich die Belastung der CPU praktisch nicht (kann aber sinken).

Auf produktiverer Hardware war es problemlos möglich, Streams mit mehr als 1Gb/s zu generieren und zu analysieren.

Testen auf echten Netzwerkkarten

Nach dem Testen auf einem Veth-Paar müssen Sie zwei Hosts oder zwei Ports eines Hosts nehmen, die Ports miteinander verbinden, den Generator auf einem und den Analysator auf dem zweiten starten. Hier gab es keine Überraschungen, aber tatsächlich kommt es auf das Eisen an, je schwächer, desto interessanter wird es hier.

Nutzung der empfangenen Daten durch das Überwachungssystem (Zabbix)

tsp verfügt über keine maschinenlesbare API wie SNMP oder ähnliches. CC-Nachrichten müssen mindestens 1 Sekunde lang aggregiert werden (bei einem hohen Prozentsatz an Paketverlusten können es je nach Bitrate Hunderte/Tausende/Zehntausende pro Sekunde sein).

Um also sowohl Informationen zu speichern als auch Diagramme für CC-Fehler und Bitrate zu zeichnen und Unfälle zu vermeiden, gibt es möglicherweise die folgenden Optionen:

  1. Analysieren und aggregieren Sie (per CC) die Ausgabe von tsp, d. h. Konvertieren Sie es in die gewünschte Form.
  2. Vervollständigen Sie tsp selbst und/oder die Prozessor-Plugins bitrate_monitor und Continuity, damit das Ergebnis in einer maschinenlesbaren Form bereitgestellt wird, die für das Überwachungssystem geeignet ist.
  3. Schreiben Sie Ihre Anwendung über die tsduck-Bibliothek.

Offensichtlich ist Option 1 vom Aufwand her am einfachsten, insbesondere wenn man bedenkt, dass tsduck selbst in einer (nach modernen Maßstäben) Low-Level-Sprache (C++) geschrieben ist.

Ein einfacher Bash-Parser+Aggregator-Prototyp zeigte, dass der Bash-Prozess bei einem 10-Mbit/s-Stream und 50 % Paketverlust (im schlimmsten Fall) drei- bis viermal mehr CPU verbrauchte als der TSP-Prozess selbst. Dieses Szenario ist inakzeptabel. Eigentlich ein Teil dieses Prototyps unten

Oben drauf Nudeln

#!/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 unannehmbar langsam ist, gibt es in Bash keine normalen Threads, Bash-Jobs sind separate Prozesse und als Nebeneffekt musste ich den Wert von MissingPackets einmal pro Sekunde schreiben (beim Empfang von Bitratennachrichten, die jede Sekunde kommen). Infolgedessen wurde Bash in Ruhe gelassen und es wurde beschlossen, einen Wrapper (Parser + Aggregator) in Golang zu schreiben. Der CPU-Verbrauch ähnlicher Golang-Codes ist 4-5 Mal geringer als der des TSP-Prozesses selbst. Die Beschleunigung des Wrappers durch den Ersatz von Bash durch Golang betrug etwa das 16-fache, und im Allgemeinen ist das Ergebnis akzeptabel (CPU-Overhead um 25 % im schlimmsten Fall). Die Golang-Quelldatei befindet sich hier.

Wrapper ausführen

Um den Wrapper zu starten, wurde die einfachste Servicevorlage für systemd erstellt (hier). Der Wrapper selbst soll in eine Binärdatei (go build tsduck-stat.go) kompiliert werden, die sich in /opt/tsduck-stat/ befindet. Es wird davon ausgegangen, dass Sie Golang mit Unterstützung für monotonen Takt (>=1.9) verwenden.

Um eine Instanz des Dienstes zu erstellen, müssen Sie den Befehl systemctl enable ausführen [E-Mail geschützt] :1234 dann mit systemctl start ausführen [E-Mail geschützt] : 1234.

Entdeckung von Zabbix

Damit zabbix laufende Dienste erkennen kann, ist dies erledigt Gruppenlistengenerator (discovery.sh), in dem für die Zabbix-Erkennung erforderlichen Format, wird davon ausgegangen, dass es sich am selben Ort befindet – in /opt/tsduck-stat. Um die Erkennung über den Zabbix-Agenten auszuführen, müssen Sie hinzufügen .conf-Datei zum zabbix-agent-Konfigurationsverzeichnis, um den Benutzerparameter hinzuzufügen.

Zabbix-Vorlage

Vorlage erstellt (tsduck_stat_template.xml) enthält die Autodiscover-Regel, Elementprototypen, Diagramme und Trigger.

Kurze Checkliste (naja, was ist, wenn sich jemand dazu entschließt, sie zu verwenden)

  1. Stellen Sie sicher, dass tsp unter „idealen“ Bedingungen (Generator und Analysator sind direkt verbunden) keine Pakete verwirft. Wenn es zu Verstößen kommt, lesen Sie Absatz 2 oder den Text des Artikels zu diesem Thema.
  2. Optimieren Sie den maximalen Socket-Puffer (net.core.rmem_max=8388608).
  3. Kompilieren Sie tsduck-stat.go (gehen Sie zum Erstellen von tsduck-stat.go).
  4. Legen Sie die Dienstvorlage in /lib/systemd/system ab.
  5. Starten Sie Dienste mit systemctl und prüfen Sie, ob Zähler angezeigt werden (grep "" /dev/shm/tsduck-stat/*). Die Anzahl der Dienste gemessen an der Anzahl der Multicast-Streams. Hier müssen Sie möglicherweise eine Route zur Multicast-Gruppe erstellen, möglicherweise rp_filter deaktivieren oder eine Route zur Quell-IP erstellen.
  6. Führen Sie discover.sh aus und stellen Sie sicher, dass JSON generiert wird.
  7. Zabbix-Agent-Konfiguration hinzufügen, Zabbix-Agent neu starten.
  8. Laden Sie die Vorlage auf zabbix hoch, wenden Sie sie auf den Host an, der überwacht wird, und der zabbix-Agent wird installiert. Warten Sie etwa 5 Minuten und prüfen Sie, ob neue Elemente, Diagramme und Auslöser vorhanden sind.

Erlebe die Kraft effektiver Ergebnisse

Verwenden von TSDuck zum Überwachen von IP(TS)-Streams

Für die Aufgabe, Paketverluste zu erkennen, reicht es fast aus, zumindest ist es besser als keine Überwachung.

Tatsächlich kann es beim Zusammenführen von Videofragmenten zu CC-„Verlusten“ kommen (soweit ich weiß, werden Einfügungen in lokalen Fernsehzentren in der Russischen Föderation auf diese Weise vorgenommen, d. h. ohne Neuberechnung des CC-Zählers). Dies muss beachtet werden. Proprietäre Lösungen umgehen dieses Problem teilweise, indem sie SCTE-35-Labels erkennen (sofern vom Stream-Generator hinzugefügt).

Im Hinblick auf die Überwachung der Transportqualität mangelt es an der Jitter-Überwachung (IAT). TV-Geräte (sei es Modulatoren oder Endgeräte) stellen Anforderungen an diesen Parameter und es ist nicht immer möglich, den Jitbuffer bis ins Unendliche aufzublasen. Und Jitter kann schweben, wenn bei der Übertragung Geräte mit großen Puffern verwendet werden und QoS nicht oder nicht gut genug konfiguriert ist, um solchen Echtzeitverkehr zu übertragen.

Source: habr.com

Kommentar hinzufügen