Użycie TSDuck do monitorowania strumieni IP(TS)

Obecnie istnieją gotowe (proprietarne) rozwiązania do monitorowania strumieni IP(TS), na przykład VB i iQ, które oferują dość bogaty zestaw funkcji, a podobne rozwiązania mają zazwyczaj duzi operatorzy zajmujący się usługami telewizyjnymi. W tym artykule opisano rozwiązanie oparte na projekcie typu open source TSDuck, przeznaczone do minimalnego monitorowania strumieni IP(TS) za pomocą licznika CC (continuity counter) i bitrate'u. Możliwą aplikacją jest kontrola utraty pakietów lub całego strumienia przez dzierżawiony kanał L2 (którego nie można normalnie monitorować, na przykład przez odczytywanie liczników utraty w kolejkach).

Bardzo krótko o TSDuck

TSDuck to open source (licencja 2-Clause BSD) oprogramowanie (zestaw narzędzi konsolowych i biblioteka do tworzenia własnych narzędzi lub wtyczek) do manipulacji strumieniami TS. Jako wejście potrafi pracować z IP (multicast/unicast), http, hls, tunerami dvb, demodulatorem dektec dvb-asi, ma wbudowany generator strumienia TS oraz możliwość odczytu z plików. Jako wyjście może być zapis do pliku, IP (multicast/unicast), hls, demodulatory dektec dvb-asi i HiDes, odtwarzacze (mplayer, vlc, xine) oraz drop. Pomiędzy wejściem a wyjściem można włączyć różne procesory ruchu, na przykład remapping PID, przeprowadzanie skremblowania/deskremblowania, analizę liczników CC, obliczanie bitrate'u i inne typowe operacje dla strumieni TS.

W tym artykule jako wejście będą używane strumienie IP (multicast), a zostaną wykorzystane procesory bitrate_monitor (z nazwy jasne, co to jest) oraz continuity (analiza liczników CC). Bez większych problemów można zastąpić IP multicast innym typem wejścia obsługiwanym przez TSDuck.

Dostępne są oficjalne zestawy/pakiety TSDuck dla większości aktualnych systemów operacyjnych. Nie ma ich dla Debiana, ale udało się bez problemów skompilować pod debian 8 i debian 10.

Następnie używana jest wersja TSDuck 3.19-1520, jako system operacyjny używany jest Linux (do przygotowania rozwiązania użyto debian 10, a do rzeczywistego użytkowania — CentOS 7)

Przygotowanie TSDuck i systemu operacyjnego

Zanim rozpoczniesz monitorowanie rzeczywistych przepływów, upewnij się, że TSDuck działa poprawnie i nie występują straty na poziomie karty sieciowej lub systemu operacyjnego (gniazda). Jest to konieczne, aby później nie zastanawiać się, gdzie wystąpiły straty — w sieci czy "wewnątrz serwera". Straty na poziomie karty sieciowej można sprawdzić komendą ethtool -S ethX, a tuning przeprowadza się tym samym narzędziem (zwykle, należy zwiększyć bufor RX (-G) i czasami wyłączyć niektóre offloady (-K)). Jako ogólną rekomendację można zasugerować użycie osobnego portu do odbioru analizowanego ruchu, o ile to możliwe, co minimalizuje fałszywe alarmy związane z tym, że strata wystąpiła konkretnie na porcie analizatora z powodu obecności innego ruchu. Jeśli taka możliwość nie istnieje (używany jest mini-komputer/NUC z jednym portem), to bardzo pożądane jest skonfigurowanie priorytetyzacji analizowanego ruchu w stosunku do pozostałego na urządzeniu, do którego podłączany jest analizator. W odniesieniu do środowisk wirtualnych należy być ostrożnym i umieć znaleźć straty pakietów, zaczynając od fizycznego portu a kończąc na aplikacji wewnątrz maszyny wirtualnej.

Generacja i odbiór strumienia wewnątrz hosta

Jako pierwszy krok przygotowania TSDuck będziemy generować i odbierać ruch wewnątrz jednego hosta z użyciem netns.

Przygotowujemy środowisko:

ip netns add P #tworzymy netns P, w nim będzie odbywać się analiza ruchu
ip link add type veth #tworzymy parę veth - veth0 zostawiamy w domyślnym netns (w tym interfejsie będzie generowany ruch)
ip link set dev veth1 netns P #veth1 - umieszczamy w netns P (na tym interfejsie będzie odbiór ruchu)
ip netns exec P ifconfig veth1 192.0.2.1/30 up #aktywizujemy IP na veth1, nie ma znaczenia, jakie dokładnie
ip netns exec P ip ro add default via 192.0.2.2 #ustalamy trasę domyślną wewnątrz netns P
sysctl net.ipv6.conf.veth0.disable_ipv6=1 #wyłączamy IPv6 na veth0 - robimy to, aby zbędne śmieci nie trafiły do licznika TX
ifconfig veth0 up #aktywujemy interfejs veth0
ip route add 239.0.0.1 dev veth0 #tworzymy trasę, aby OS kierował ruch do 239.0.0.1 w stronę veth0

Środowisko gotowe. Uruchamiamy analizator ruchu:

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

gdzie „-p 1 -t 1” oznacza, że należy obliczać bitrate co sekundę i wyświetlać informacje o bitrate co sekundę.
Uruchamiamy generator ruchu z prędkością 10Mbit/s:

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

gdzie „-p 7 -e” oznacza, że trzeba pakować 7 pakietów TS w 1 pakiet IP i robić to sztywno (-e), czyli zawsze czekać na 7 pakietów TS od ostatniego procesora przed wysłaniem pakietu IP.

Analizator zaczyna wyświetlać oczekiwane komunikaty:

* 2020/01/03 14:55:44 - bitrate_monitor: 2020/01/03 14:55:44, bitrate TS: 9,970,016 bitów/s
* 2020/01/03 14:55:45 - bitrate_monitor: 2020/01/03 14:55:45, bitrate TS: 10,022,656 bitów/s
* 2020/01/03 14:55:46 - bitrate_monitor: 2020/01/03 14:55:46, bitrate TS: 9,980,544 bitów/s

Teraz dodajemy trochę strat:

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

i pojawiają się komunikaty takich typów:

* 2020/01/03 14:57:11 - continuity: indeks pakietu: 80,745, PID: 0x0000, brak 7 pakietów
* 2020/01/03 14:57:11 - continuity: indeks pakietu: 83,342, PID: 0x0000, brak 7 pakietów 

co jest oczekiwane. Wyłączamy zgubę pakietów (ip netns exec P iptables -F) i próbujemy zwiększyć bitrate generatora do 100 Mbit/s. Analizator raportuje wiele błędów CC i około 75 Mbit/s zamiast 100. Próba ustalenia, kto jest winny — czy to generator nie nadąża, czy może problem leży gdzie indziej, w tym celu uruchamiamy generowanie stałej liczby pakietów (700000 pakietów TS = 100000 pakietów IP):

# 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

Jak widać, wygenerowano dokładnie 100000 pakietów IP (151925460-151825460). Dlatego sprawdzamy, co się dzieje z analizatorem, porównując z licznikem RX na veth1, który jest dokładnie równy licznikowi TX na veth0, następnie sprawdzamy, co się dzieje na poziomie gniazda:

# 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 

Tutaj widzimy liczbę zgubów = 24355. W pakietach TS to 170485, czyli 24.36% z 700000, tak więc widzimy, że te 25% utraconego bitrate to zguby w gniazdach UDP. Zguby w gniazdach UDP zazwyczaj wynikają z braku bufora, sprawdzamy, jaki jest domyślny rozmiar bufora gniazda oraz maksymalny rozmiar bufora gniazda:

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

W związku z tym, jeśli aplikacje nie żądają wyraźnie rozmiaru bufora, gniazda są tworzone z buforem o wielkości 208 KB, ale jeśli poproszą o więcej, i tak go nie otrzymają. Ponieważ w tsp dla wejścia IP można określić rozmiar bufora (—buffer-size), nie będziemy dotykać domyślnego rozmiaru gniazda, a jedynie określimy maksymalny rozmiar bufora gniazda i podamy rozmiar bufora wyraźnie przez argumenty tsp:

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

Z takim dostosowaniem bufora gniazda teraz raportowany bitrate wynosi około 100 Mbit/s, błędów CC nie ma.

Według zużycia CPU przez aplikację tsp. W przypadku jednego rdzenia i5-4260U CPU @ 1.40GHz do analizy strumienia 10 Mb/s potrzebne będzie 3-4% CPU, 100 Mb/s — 25%, 200 Mb/s — 46%. Przy ustawionym procencie utraty pakietów obciążenie CPU praktycznie się nie zwiększa (może się jednak zmniejszać).

Na bardziej wydajnym sprzęcie udało się bez problemu generować i analizować strumienie powyżej 1 Gb/s.

Testowanie na rzeczywistych kartach sieciowych

Po testowaniu na parze veth należy wziąć dwa hosty lub dwa porty jednego hosta, połączyć porty ze sobą, na jednym uruchomić generator, na drugim analizator. Tutaj nie było niespodzianek, ale tak naprawdę wszystko zależy od sprzętu — im słabszy, tym bardziej interesująco będzie w tym przypadku.

Wykorzystanie uzyskanych danych w systemie monitorowania (Zabbix)

Tsp nie ma żadnego interfejsu API typu machine-readable, takiego jak SNMP czy podobnego. Wiadomości CC należy agregować przynajmniej co 1 sekundę (przy wysokim procencie utraty pakietów może ich być setki/tysiące/dziesiąt tysięcy na sekundę, w zależności od bitratu).

Aby zachować informacje i narysować wykresy dotyczące błędów CC i bitratu oraz zrealizować jakieś awarie, mogą być następujące opcje:

  1. Przeanalizować i zintegrować (według CC) wyjście tsp, tzn. przekształcić je w odpowiednią formę.
  2. Dostosować sam tsp i/lub pluginy procesora bitrate_monitor i continuity, aby wynik był podawany w formie machine-readable, odpowiedniej dla systemu monitorowania.
  3. Napisać własną aplikację na bazie biblioteki tsduck.

Jest oczywiste, że z punktu widzenia nakładu pracy opcja 1 jest najprostsza, szczególnie biorąc pod uwagę, że sam tsduck jest napisany w niskopoziomowym języku (jak na dzisiejsze standardy) (C++).

Prosty prototyp parsera i agregatora w Bashu pokazał, że przy strumieniu 10 Mb/s i 50% utraty pakietów (najgorszy scenariusz) proces bash zużywał 3-4 razy więcej CPU niż sam proces tsp. Taki rozwój wypadków jest nie do przyjęcia. Oto fragment tego prototypu poniżej

Makaron na bashu

#!/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

Oprócz tego, że działa to nieakceptowalnie wolno, bash nie ma normalnych wątków, zadania bash są niezależnymi procesami i musiałem zapisywać co sekundę wartość missingPackets w efekcie ubocznym (przy odbieraniu wiadomości o bitrate, które przychodzą co sekundę). Ostatecznie bash został odłożony i postanowiono napisać wrapper (parser + agregator) w golang. Zużycie CPU porównywalnego kodu w golang jest 4-5 razy mniejsze niż samego procesu tsp. Przyspieszenie wrappera dzięki zastąpieniu bash golangiem wyniosło około 16 razy, a ogólny rezultat jest akceptowalny (overhead CPU na poziomie 25% w najgorszym przypadku). Plik źródłowy w golang znajduje się tutaj.

Uruchomienie wrappera

Aby uruchomić wrapper, stworzono najprostszy szablon usługi dla systemd (tutaj). Zakłada się, że sam wrapper jest skompilowany do pliku binarnego (go build tsduck-stat.go), umieszczonego w /opt/tsduck-stat/. Zakłada się, że używany jest golang z obsługą monotonic clock (>=1.9).

Aby stworzyć egzemplarz usługi, należy wykonać polecenie systemctl enable tsduck-stat@239.0.0.1:1234, a następnie uruchomić za pomocą systemctl start tsduck-stat@239.0.0.1:1234.

Odkrycie z Zabbixa

Aby Zabbix mógł zrobić odkrycie uruchomionych usług, stworzono generator listy grup (discovery.sh), w formacie wymaganym dla odkrycia Zabbixa, zakłada się, że jest umieszczony tam samej — w /opt/tsduck-stat. Aby uruchomić odkrycie za pośrednictwem agenta Zabbixa, należy dodać .conf-pliki do katalogu z konfiguracjami agenta Zabbixa w celu dodania parametru użytkownika.

Szablon Zabbixa

Stworzony szablon (tsduck_stat_template.xml) zawiera zasady auto-odkrycia, prototypy elementów danych, wykresów i wyzwalaczy.

Krótka lista kontrolna (na wszelki wypadek, gdyby ktoś postanowił z niej skorzystać)

  1. Upewnić się, że tsp nie gubi pakietów w "idealnych" warunkach (generator i analizator podłączone bezpośrednio), jeśli są straty, patrz p.2 lub tekst artykułu na ten temat.
  2. Dostosować maksymalny bufor gniazda (net.core.rmem_max=8388608).
  3. Skompilować tsduck-stat.go (go build tsduck-stat.go).
  4. Umieścić szablon usługi w /lib/systemd/system.
  5. Uruchomić usługi za pomocą systemctl, sprawdzić, czy zaczynają pojawiać się liczniki (grep "" /dev/shm/tsduck-stat/*). Liczba usług odpowiada liczbie strumieni multicastowych. Może być konieczne utworzenie trasy do grupy multicastowej, możliwe, że wyłączenie rp_filter lub utworzenie trasy do adresu IP źródłowego.
  6. Uruchomić discovery.sh, upewnić się, że generuje json.
  7. Podłożyć konfigurację agenta zabbix, zrestartować agenta zabbixa.
  8. Załaduj szablon do Zabbix, zastosuj go do hosta, na którym prowadzi się monitoring i jest zainstalowany zabbix-agent, poczekaj około 5 minut, a następnie sprawdź, czy pojawiły się nowe elementy danych, wykresy i wyzwalacze.

Wynik

Użycie TSDuck do monitorowania strumieni IP(TS)

W przypadku wykrywania utraty pakietów prawie wystarczy, w każdym razie jest to lepsze niż brak monitoringu.

Rzeczywiście, CC-„straty” mogą występować podczas łączenia fragmentów wideo (o ile mi wiadomo, tak wykonuje się wstawki w lokalnych centrach telewizyjnych w Rosji, czyli bez przeliczania licznika CC), warto o tym pamiętać. W rozwiązaniach proprietarnych ten problem jest częściowo omijany dzięki wykrywaniu znaczników SCTE-35 (jeżeli są one dodawane przez generator strumienia).

Z perspektywy monitoringu jakości transportu brakuje monitorowania jitter (IAT), ponieważ sprzęt telewizyjny (czy to modulatory, czy urządzenia końcowe) ma wymagania co do tego parametru, a rozbudowa jitbuffer do nieskończoności nie zawsze jest możliwa. A jitter może się pogorszyć, gdy w tranzycie używa się sprzętu o dużych buforach i QoS do przesyłania tego rodzaju ruchu w czasie rzeczywistym nie jest dobrze skonfigurowany.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster