Oggi esistono soluzioni pronte (proprietarie) per il monitoraggio dei flussi IP(TS), come ad esempio e , che offrono un insieme piuttosto ricco di funzionalità e generalmente tali soluzioni sono disponibili presso i grandi operatori che gestiscono servizi TV. In questo articolo viene presentata una soluzione basata sul progetto open source , progettata per un controllo minimo dei flussi IP(TS) tramite il contatore CC (continuity counter) e il bitrate. Una possibile applicazione è il monitoraggio della perdita di pacchetti o dell'intero flusso attraverso un canale L2 affittato (che non può essere monitorato normalmente, ad esempio tramite la lettura dei contatori di perdita nelle code).
In breve su TSDuck
TSDuck è un software open source (licenza BSD a 2 clausole) composto da un insieme di utilità da console e librerie per sviluppare le proprie utilità o plugin per la manipolazione dei flussi TS. Come input, è in grado di lavorare con IP (multicast/unicast), http, hls, sintonizzatori dvb, demodulatore dektec dvb-asi, e include un generatore interno di flussi TS oltre alla lettura da file. Come output, può scrivere su file, IP (multicast/unicast), hls, demodulatori dektec dvb-asi e HiDes, lettori (mplayer, vlc, xine) e drop. Tra l'input e l'output è possibile inserire vari processori di traffico, come il rimappamento dei PID, effettuare scrambler/descrambler, analizzare i contatori CC, calcolare il bitrate e altre operazioni tipiche per i flussi TS.
In questo articolo, come input, saranno utilizzati flussi IP (multicast), con i processori bitrate_monitor (dal nome è chiaro di cosa si tratti) e continuity (analisi dei contatori CC). Senza particolari problemi, è possibile sostituire l'IP multicast con un altro tipo di input supportato da TSDuck.
Ci sono TSDuck per la maggior parte dei sistemi operativi attuali. Non sono disponibili per Debian, ma sono riuscito a compilarne senza problemi sotto debian 8 e debian 10.
La versione utilizzata di TSDuck è 3.19-1520, il sistema operativo è Linux (per la preparazione della soluzione è stato utilizzato Debian 10, per l'uso reale — CentOS 7)
Preparazione di TSDuck e del sistema operativo
Prima di monitorare i flussi reali, è necessario assicurarsi che TSDuck funzioni correttamente e che non ci siano perdite a livello della scheda di rete o del sistema operativo (socket). Questo è necessario per evitare di dover indovinare dove si sono verificate le perdite — nella rete o "all'interno del server". Per controllare le perdite a livello della scheda di rete, è possibile utilizzare il comando ethtool -S ethX; la regolazione viene effettuata sempre con ethtool (di solito è necessario aumentare il buffer RX (-G) e, a volte, disattivare alcuni offload (-K)). Come raccomandazione generale, si consiglia di utilizzare una porta separata per ricevere il traffico analizzato, se possibile, poiché questo minimizza i falsi allarmi legati al fatto che la perdita è avvenuta proprio sulla porta dell'analizzatore a causa della presenza di altro traffico. Se questa possibilità non esiste (si utilizza un mini-computer/NUC con una sola porta), è molto desiderabile configurare la priorità del traffico analizzato rispetto al resto del dispositivo a cui è collegato l'analizzatore. Per quanto riguarda gli ambienti virtuali, è necessario essere cauti e saper trovare le perdite di pacchetti a partire dalla porta fisica fino all'applicazione all'interno della macchina virtuale.
Generazione e ricezione del flusso all'interno dell'host
Come primo passo nella preparazione, useremo TSDuck per generare e ricevere traffico all'interno di un singolo host utilizzando netns.
Prepariamo l'ambiente:
ip netns add P #creiamo netns P, qui avverrà l'analisi del traffico
ip link add type veth #creiamo una coppia veth - veth0 rimane nel netns di default (su cui sarà generato il traffico)
ip link set dev veth1 netns P #veth1 - collochiamo nel netns P (su questo interfaccia avverrà la ricezione del traffico)
ip netns exec P ifconfig veth1 192.0.2.1/30 up #attiviamo IP su veth1, non importa quale
ip netns exec P ip ro add default via 192.0.2.2 #configuriamo la rotta predefinita all'interno di netns P
sysctl net.ipv6.conf.veth0.disable_ipv6=1 #disabilitiamo IPv6 su veth0 - questo per evitare che nel contatore TX finiscano dati estranei
ifconfig veth0 up #attiviamo l'interfaccia veth0
ip route add 239.0.0.1 dev veth0 #creiamo una rotta affinché il sistema operativo reindirizzi il traffico a 239.0.0.1 attraverso veth0L'ambiente è pronto. Avviamo l'analizzatore di traffico:
ip netns exec P tsp --realtime -t
-I ip 239.0.0.1:1234
-P continuity
-P bitrate_monitor -p 1 -t 1
-O dropdove «-p 1 -t 1» significa che bisogna calcolare il bitrate ogni secondo e fornire informazioni sul bitrate ogni secondo
Avviamo il generatore di traffico con una velocità di 10 Mbit/s:
tsp -I craft
-P regulate -b 10000000
-O ip -p 7 -e --local-port 6000 239.0.0.1:1234dove «-p 7 -e» significa che bisogna impacchettare 7 pacchetti TS in 1 pacchetto IP e farlo rigidamente (-e), cioè aspettare sempre 7 pacchetti TS dall'ultimo processore prima di inviare la formazione del pacchetto IP.
L'analizzatore inizia a restituire i messaggi attesi:
* 2020/01/03 14:55:44 - bitrate_monitor: 2020/01/03 14:55:44, bitrate TS: 9,970,016 bits/s
* 2020/01/03 14:55:45 - bitrate_monitor: 2020/01/03 14:55:45, bitrate TS: 10,022,656 bits/s
* 2020/01/03 14:55:46 - bitrate_monitor: 2020/01/03 14:55:46, bitrate TS: 9,980,544 bits/sOra aggiungiamo un po' di drop:
ip netns exec P iptables -I INPUT -d 239.0.0.1 -m statistic --mode random --probability 0.001 -j DROPe appaiono messaggi di tipo:
* 2020/01/03 14:57:11 - continuity: indice pacchetto: 80,745, PID: 0x0000, mancano 7 pacchetti
* 2020/01/03 14:57:11 - continuity: indice pacchetto: 83,342, PID: 0x0000, mancano 7 pacchetti che è atteso. Disattiviamo la perdita di pacchetti (ip netns exec P iptables -F) e proviamo ad aumentare il bitrate del generatore a 100 Mbit/s. L'analizzatore riporta un sacco di errori CC e circa 75 Mbit/s invece di 100. Proviamo a capire chi è il colpevole — se è il generatore che non tiene il passo oppure se il problema non è in lui, quindi avviamo la generazione di un numero fisso di pacchetti (700000 pacchetti TS = 100000 pacchetti 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 0Come si può vedere, sono stati generati esattamente 100.000 pacchetti IP (151925460-151825460). Quindi, analizziamo cosa sta succedendo con l'analizzatore, confrontando il contatore RX su veth1, che corrisponde esattamente al contatore TX su veth0. Successivamente, vediamo cosa accade a livello di socket:
# 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 Qui si può vedere che il numero di drop è 24355. Nei pacchetti TS, questo equivale a 170485 o 24,36% su 700000. In questo modo, vediamo che quel 25% di bitrate perso sono i drop nel socket UDP. I drop nel socket UDP di solito si verificano a causa di un buffer insufficienti, controlliamo quale sia la dimensione del buffer del socket predefinito e la dimensione massima del buffer del socket:
# sysctl net.core.rmem_default
net.core.rmem_default = 212992
# sysctl net.core.rmem_max
net.core.rmem_max = 212992Quindi, se le applicazioni non richiedono esplicitamente la dimensione del buffer, i socket vengono creati con un buffer di dimensione 208 KB, ma se viene richiesta una dimensione maggiore, non verrà comunque fornito quanto richiesto. Poiché in tsp per l'ingresso IP si può impostare la dimensione del buffer (—buffer-size), non toccheremo la dimensione predefinita del socket, ma imposteremo solo la dimensione massima del buffer del socket e specificheremo la dimensione del buffer esplicitamente tramite gli argomenti 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 dropCon questa configurazione del buffer del socket, il bitrate riportato ora è di circa 100 Mbit/s, non ci sono errori CC.
Per il consumo della CPU dall'applicazione stessa tsp. Per analizzare un flusso di 10 Mbit/s su un CPU i5-4260U @ 1.40GHz saranno necessari circa il 3-4% di CPU; per 100 Mbit/s il 25%; per 200 Mbit/s il 46%. L'impatto della CPU non aumenta praticamente con la percentuale di perdita di pacchetti (ma può diminuire).
Su hardware più potente, è stato possibile generare e analizzare senza problemi flussi superiori a 1 Gbit/s.
Test su schede di rete reali
Dopo il testing su una coppia veth, è necessario prendere due host o due porte di uno stesso host, collegare le porte tra loro, eseguire un generatore su una e un analizzatore sull'altra. Qui non ci sono state sorprese, ma in realtà tutto dipende dall'hardware; più è debole, più interessante sarà.
Utilizzo dei dati raccolti nel sistema di monitoraggio (Zabbix)
Tsp non ha alcun API machine-readable come SNMP o simili. I messaggi CC devono essere aggregati almeno ogni secondo (in caso di alta percentuale di perdita di pacchetti, possono essere centinaia/migliaia/diecimila al secondo, a seconda del bitrate).
Pertanto, per mantenere le informazioni e tracciare i grafici sugli errori CC e sul bitrate, e per gestire eventuali incidenti, ci possono essere le seguenti opzioni:
- Analizzare e aggregare (per CC) l'output di tsp, cioè trasformarlo nella forma necessaria.
- Migliorare il codice di tsp e/o i plugin del processore bitrate_monitor e continuity, affinché il risultato venga fornito in una forma leggibile dalle macchine, adatta per il sistema di monitoraggio.
- Scrivere la propria applicazione sopra la libreria tsduck.
È evidente che, in termini di lavoro, la prima opzione è la più semplice, soprattutto considerando che tsduck è scritto in un linguaggio di basso livello (secondo gli standard moderni) (C++).
Un semplice prototipo di parser+aggregatore in bash ha mostrato che su un flusso di 10Mbit/s e il 50% di perdita di pacchetti (il caso peggiore), il processo bash consumava da 3 a 4 volte più CPU rispetto al processo tsp stesso. Questo scenario è inaccettabile. Qui di seguito c'è un frammento di questo prototipo.
Noodles 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
doneOltre a funzionare in modo inaccettabilmente lento, bash non ha thread appropriati, i lavori bash sono processi autonomi e ho dovuto registrare il valore missingPackets ogni secondo come effetto collaterale (quando ricevo un messaggio sul bitrate, che arriva ogni secondo). Di conseguenza, bash è stato abbandonato e abbiamo deciso di scrivere un wrapper (parser + aggregatore) in golang. Il consumo di CPU del codice equivalente in golang è 4-5 volte inferiore a quello del processo tsp. L'accelerazione del wrapper, sostituendo bash con golang, è stata di circa 16 volte e in generale il risultato è accettabile (overhead della CPU del 25% nel caso peggiore). Il file sorgente in golang si trova .
Avvio del wrapper
Per avviare il wrapper è stato creato un semplice modello di servizio per systemd (). Si presume che il wrapper stesso sia stato compilato in un file binario (go build tsduck-stat.go), posizionato in /opt/tsduck-stat/. Si presuppone che si utilizzi golang con supporto per clock monotonic (>= 1.9).
Per creare un'istanza del servizio è necessario eseguire il comando systemctl enable tsduck-stat@239.0.0.1:1234, quindi avviarlo con systemctl start tsduck-stat@239.0.0.1:1234.
Discovery da Zabbix
Affinché zabbix possa effettuare la discovery dei servizi in esecuzione, è stato creato un (discovery.sh), in un formato necessario per la scoperta di Zabbix, si presume sia collocato lì — in /opt/tsduck-stat. Per eseguire la scoperta tramite zabbix-agent, è necessario aggiungere nella directory delle configurazioni dello zabbix-agent per aggiungere il parametro utente.
Template Zabbix
(tsduck_stat_template.xml) contiene una regola di auto-scoperta, prototipi di elementi dati, grafici e trigger.
Breve checklist (nel caso qualcuno decida di usufruirne)
- Assicurati che tsp non scarti pacchetti in condizioni 'ideali' (generatori e analizzatori collegati direttamente), se ci sono scarti, vedere p.2 o il testo dell'articolo a riguardo.
- Ottimizzare il buffer massimo del socket (net.core.rmem_max=8388608).
- Compilare tsduck-stat.go (go build tsduck-stat.go).
- Posizionare il template del servizio in /lib/systemd/system.
- Avviare i servizi con systemctl, controllare che inizino ad apparire i contatori (grep «» /dev/shm/tsduck-stat/*). Il numero dei servizi deve corrispondere al numero dei flussi multicast. Potrebbe essere necessario creare una rotta verso il gruppo multicast, possibilmente disabilitare rp_filter o creare una rotta verso l'ip sorgente.
- Eseguire discovery.sh e assicurarsi che generi json.
- Fornire la configurazione dello zabbix-agent e riavviare lo zabbix-agent.
- Carica il template in Zabbix, applicalo all'host monitorato dove è installato Zabbix Agent, attendi circa 5 minuti, poi verifica se sono apparsi nuovi elementi di dati, grafici e trigger.
Risultato

Per rilevare la perdita di pacchetti, è quasi sufficiente; in effetti, è meglio che non avere alcun monitoraggio.
In effetti, le "perdite" CC possono verificarsi durante l'assemblaggio di frammenti video (per quanto ne so, è così che vengono effettuate le inserzioni nei centri televisivi locali in RF, cioè senza ricalcolare il contatore CC); bisogna tenerlo a mente. Nelle soluzioni proprietarie, questo problema viene parzialmente aggirato con la rilevazione delle etichette SCTE-35 (se vengono aggiunte dal generatore di flusso).
Dal punto di vista del monitoraggio della qualità del trasporto, manca il monitoraggio del jitter (IAT), poiché l'attrezzatura TV (sia i modulatori che i dispositivi finali) ha requisiti per questo parametro e non sempre è possibile espandere il jitbuffer all'infinito. E il jitter può diventare instabile quando nell'instradamento viene utilizzata attrezzatura con buffer grandi e QoS non è impostato correttamente o non è sufficientemente ben configurato per trasferire traffico in tempo reale.
Fonte: habr.com
