Obecnie istnieją gotowe (proprietarne) rozwiązania do monitorowania strumieni IP(TS), na przykład i , 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 , 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ą 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 dropgdzie „-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:1234gdzie „-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/sTeraz dodajemy trochę strat:
ip netns exec P iptables -I INPUT -d 239.0.0.1 -m statistic --mode random --probability 0.001 -j DROPi 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 0Jak 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 = 212992W 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 dropZ 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:
- Przeanalizować i zintegrować (według CC) wyjście tsp, tzn. przekształcić je w odpowiednią formę.
- Dostosować sam tsp i/lub pluginy procesora bitrate_monitor i continuity, aby wynik był podawany w formie machine-readable, odpowiedniej dla systemu monitorowania.
- 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
doneOpró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ę .
Uruchomienie wrappera
Aby uruchomić wrapper, stworzono najprostszy szablon usługi dla systemd (). 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 (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ć do katalogu z konfiguracjami agenta Zabbixa w celu dodania parametru użytkownika.
Szablon Zabbixa
(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ć)
- 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.
- Dostosować maksymalny bufor gniazda (net.core.rmem_max=8388608).
- Skompilować tsduck-stat.go (go build tsduck-stat.go).
- Umieścić szablon usługi w /lib/systemd/system.
- 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.
- Uruchomić discovery.sh, upewnić się, że generuje json.
- Podłożyć konfigurację agenta zabbix, zrestartować agenta zabbixa.
- 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

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
