Выкарыстанне TSDuck для маніторынгу IP(TS)-струменяў

На сённяшні дзень існуюць гатовыя (прапрыетарныя) рашэнні для маніторынгу IP(TS)-струменяў, напрыклад VB и iQ, Яны валодаюць дастаткова багатым наборам функцый і звычайна падобныя рашэнні маюцца ў буйных аператараў, якія маюць справу з ТБ-сэрвісамі. У гэтым артыкуле апісваецца рашэнне на базе open source праекту TSDuck, Прызначанае для мінімальнага кантролю IP(TS)-струменяў па лічыльніку CC(continuity counter) і бітрэйту. Магчымы варыянт прымянення - кантроль страты пакетаў або патоку цалкам праз арандаваны L2-канал (які няма магчымасці нармальна маніторыць, напрыклад шляхам счытвання лічыльнікаў страт у чэргах).

Вельмі коратка аб TSDuck

TSDuck гэта open source (ліцэнзія 2-Clause BSD) ПЗ (набор кансольных утыліт і бібліятэка для распрацоўкі сваіх утыліт або плагінаў) для маніпуляцый з TS-струменямі. У якасці ўваходу ўмее працаваць з IP (multicast/unicast), http, hls, dvb-цюнарамі, dektec dvb-asi дэмадулятарам, маецца ўнутраны генератар TS-струменю і чытанне з файлаў. У якасці вынахаду можа быць запіс у файл, IP (multicast/unicast), hls, dektec dvb-asi і HiDes мадулятары, плэеры (mplayer, vlc, xine) і drop. Паміж уваходам і выхадам можна ўключыць розныя працэсары трафіку, напрыклад перамапінг PID-ов, рабіць скремблирование/дэскрэмбаванне, аналіз CC-лічыльнікаў, падлік бітрэйту і іншыя тыпавыя для TS-струменяў аперацыі.

У дадзеным артыкуле ў якасці ўваходу будуць IP-струмені (multicast), скарыстаны працэсары bitrate_monitor (з назову зразумела што гэта такое) і continuity (аналіз CC-лічыльнікаў). Без асаблівых праблем можна замяніць IP multicast на іншы тып уваходу, які падтрымліваецца TSDuck.

Маюцца афіцыйныя зборкі/пакеты TSDuck для большасці актуальных АС. Для Debian іх няма, але ўдалося без праблем сабраць пад debian 8 і debian 10.

Далей выкарыстоўваецца версія TSDuck 3.19-1520, у якасці АС выкарыстоўваецца Linux (для падрыхтоўкі рашэння выкарыстоўваўся debian 10, для рэальнага выкарыстання - CentOS 7)

Падрыхтоўка TSDuck і АС

Перш чым маніторыць рэальныя патокі трэба пераканацца, што TSDuck карэктна працуе і не адбываюцца дропы на ўзроўні сеткавай карты або АС (сокета). Гэта патрабуецца для таго, каб потым не варажыць дзе адбыліся дропы - на сеткі або "ўнутры сервера". Правяраць дропы на ўзроўні сеткавай карты можна камандай ethtool -S ethX, цюнінг робіцца той жа ethtool (звычайна, трэба павялічваць RX-буфер (-G) і часам адключаць некаторыя offloads (-K)). У якасці агульнай рэкамендацыі можна параіць выкарыстоўваць асобны порт для прыёму аналізаванага трафіку, калі маецца такая магчымасць, гэта мінімізуе ілжывыя спрацоўванні злучаныя з тым, што дроп здарыўся какрэнтна на порце аналізатара з-за наяўнасці іншага трафіку. Калі такой магчымасці няма (выкарыстоўваецца міні-кампутар/NUC з адным портам), то вельмі пажадана наладзіць прыярытэзацыю аналізаванага трафіку па стаўленні да астатняга на прыладзе, у які падлучаецца аналізатар. Адносна віртуальных асяроддзяў, тут трэба быць асцярожным і ўмець знаходзіць дропы пакета пачынаючы ад фізічнага порта і заканчваючы дадаткам ўнутры віртуальнай машыны.

Генерацыя і прыём патоку ўнутры хаста

У якасці першага кроку падрыхтоўкі TSDuck будзем генераваць і прымаць трафік усярэдзіне аднаго хаста з выкарыстаннем netns.

Рыхтуем асяроддзе:

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

Асяроддзе гатова. Запускаем аналізатар трафіку:

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

дзе "-p 1 -t 1" азначае, што трэба разлічваць бітрэйт кожную секунду і выводзіць інфармацыю аб бітрэйце кожную секунду
Запускаем генератар трафіку са хуткасць 10Мбіт/з:

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

дзе "-p 7 -e" азначае, што трэба пакаваць па 7 TS-пакетаў у 1 IP-пакет і рабіць гэта цвёрда (-e), г.зн. заўсёды чакаць 7 TS-пакетаў ад апошняга працэсара перад адпраўкай фармаваннем IP-пакета.

Аналізатар пачынае выводзіць чаканыя паведамленні:

* 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

Цяпер дадаем крыху драпаў:

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

і з'яўляюцца паведамленні тып такіх:

* 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 

што з'яўляецца чаканым. Адключаем страту пакетаў (ip netns exec P iptables -F) і спрабуем павялічыць бітрэйт генератара да 100Мбіт/з. Аналізатар рапартуе кучу CC-памылак і каля 75 Мбіт/з замест 100. Спрабуем разабрацца хто вінаваты - не паспявае генератар ці ж праблема не ў ім, для гэтага запускаем генерацыю фіксаванай колькасці пакетаў (700000 TS-пакетаў = 100000 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

Як відаць, было згенеравана роўна 100000 IP-пакетаў (151925460-151825460). Значыць разбіраемся што адбываецца з аналізатарам, для гэтага звяраем са лічыльнікам RX на veth1, ён строга роўны лічыльніку TX на veth0, далей глядзім што адбываецца на ўзроўні сокета:

# 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 

Тут бачна колькасць дропаў = 24355. У TS-пакетах гэта 170485 або 24.36% ад 700000, такім чынам бачым што тыя самыя 25% страчанага бітрэйту гэта дропы ў udp-сакеце. Дропы ў UDP-сакеце звычайна ўзнікаюць з-за недахопу буфера, глядзім чаму роўны памер буфера сокета па змаўчанні і максімальны памер буфера сокета:

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

Такім чынам, калі прыкладанні не запытваюць памер буфера відавочна, сокеты ствараюцца з буферам памерам 208 Кб, але калі запытаюць больш, то ўсё роўна не атрымаюць запытанае. Паколькі ў tsp для IP-уваходу можна задаць памер буфера (-buffer-size), то памер сокета па змаўчанні чапаць не будзем, а толькі зададзім максімальны памер буфера сокета і пакажам памер буфера відавочна праз аргументы 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

З такім цюнінгам буфера сокета зараз рапартаваны бітрэйт складае прыкладна 100Мбіт/з, CC-памылак няма.

Па спажыванні CPU самім дадаткам tsp. Адносна аднаго ядра i5-4260U CPU @ 1.40GHz для аналізу струменя 10Мбіт/з запатрабуецца 3-4% CPU, 100 Мбіт/з - 25%, 200Мбіт/з - 46%. Пры заданні% страты пакетаў, нагрузка на CPU практычна не павялічваецца (але можа памяншацца).

На больш прадукцыйным жалезе ўдавалася без праблем генераваць і аналізаваць патокі больш за 1Гб / с.

Тэставанне на рэальных сеткавых картах

Пасля тэставання на veth-пары трэба ўзяць два хасты ці два порта аднаго хаста, злучыць парты паміж сабой, на адным запусціць генератар, на другім аналізатар. Тут нечаканасцяў не здарылася, але насамрэч усё залежыць ад жалеза, чым слабей, тым цікавей тут будзе.

Выкарыстанне атрыманых дадзеных сістэмай маніторынгу (Zabbix)

У tsp няма які-небудзь machine-readable API тыпу SNMP ці падобнага. Паведамленні CC трэба агрэгаваць хаця б па 1 секундзе (пры высокім працэнце страты пакетаў, іх можа быць сотні/тысяч/дзясяткі тысяч у секунду, залежыць ад бітрэйту).

Такім чынам, каб захоўваць і інфармацыю і намаляваць графікі па CC-памылках і бітрэйту і зрабіць нейкія аварыі далей могуць быць наступныя варыянты:

  1. Распарсіць і сагрэгаваць (па CC) выснова tsp, г.зн. пераўтварыць яго ў патрэбную форму.
  2. Дапілаваць сам tsp і/ці працэсарныя плагіны bitrate_monitor і continuity, каб вынік выдаваўся ў machine-readable форме, прыдатнай для сістэмы маніторынгу.
  3. Напісаць сваё прыкладанне па-над бібліятэкай tsduck.

Відавочна, што з кропкі працавыдаткаў варыянт 1 самы просты, асабліва улічваючы тое, што сам tsduck напісаны на нізкаўзроўневай (па сучасных мерках) мове (C++)

Прасценькі прататып парсера+агрэгатара на bash паказаў, што на струмені 10Мбіт/з і 50% страты пакетаў (найгоршы варыянт), працэс bash спажываў у 3-4 разу больш CPU, чым сам працэс tsp. Такі варыянт развіцця падзей непрымальны. Уласна кавалак гэтага прататыпа ніжэй

Локшына на баше

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

Акрамя таго, што гэта працуе недапушчальна павольна, у bash адсутнічаюць нармальныя ніткі, bash jobs з'яўляюцца самастойнымі працэсамі і прыйшлося рабіць запіс раз у секунду значэння missingPackets на сайд-эфекце (пры атрыманні паведамлення аб бітрэйце, якія прыходзяць кожную секунду). У выніку, bash быў пакінуты ў спакоі і было вырашана напісаць абгортку (парсер+агрэгатар) на golang. Спажыванне CPU аналагічнага кода на golang у 4-5 разоў менш, чым самога працэсу tsp. Паскарэнне абгорткі за кошт замены bash на golang атрымалася прыкладна ў 16 разоў і ў цэлым вынік прымальны (оверхед па CPU на 25% у найгоршым выпадку). Зыходны файл на golang знаходзіцца тут.

Запуск абгорткі

Для запуску абгорткі зроблены найпросты шаблон сэрвісу для systemd (тут). Мяркуецца, што сама абгортка скампіляваная ў бінарны файл (go build tsduck-stat.go), размешчаны ў /opt/tsduck-stat/. Мяркуецца, што выкарыстоўваецца golang з падтрымкай monotonic clock (>=1.9).

Каб стварыць асобнік сэрвісу трэба выканаць каманду systemctl enable [электронная пошта абаронена]:1234, затым запусьціць з дапамогай systemctl start [электронная пошта абаронена]: 1234.

Discovery з Zabbix

Каб zabbix мог рабіць дыскаўеры запушчаных сэрвісаў, зроблены генератар спісу груп (discovery.sh), у фармаце неабходным для Zabbix discovery, мяркуецца што ён размешчаны там жа – у /opt/tsduck-stat. Каб запускаць discovery праз zabbix-agent, трэба дадаць .conf-файл у дырэкторыю з канфігурацыямі zabbix-agent'а для дадання user-параметра.

Шаблон Zabbix

Створаны шаблон (tsduck_stat_template.xml) утрымоўвае правіла аўтавыяўлення, прататыпы элементаў дадзеных, графікаў і трыгераў.

Кароткі чэкліст (ну а раптам нехта вырашыць скарыстацца)

  1. Пераканацца што tsp не драпае пакеты ў "ідэальных" умовах (генератар і аналізатар падлучаныя напроста), калі ёсць дропы гл. п.2 ці тэкст артыкула па гэтай нагодзе.
  2. Зрабіць цюнінг максімальнага буфера сокета (net.core.rmem_max=8388608).
  3. Скампіляваць tsduck-stat.go (go build tsduck-stat.go).
  4. Пакласці шаблон сэрвісу ў /lib/systemd/system.
  5. Запусціць сэрвісы з дапамогай systemctl, праверыць што пачалі з'яўляцца лічыльнікамі (grep "" /dev/shm/tsduck-stat/*). Колькасць сэрвісаў па колькасці мультыкаст-струменяў. Тут можа запатрабавацца стварыць маршрут да мультыкаст-групы, магчыма, адключыць rp_filter або стварыць маршрут да source ip.
  6. Запусціць discovery.sh, пераканацца, што ён генеруе json.
  7. Падкласці канфіг zabbix-агента, перазапусціць zabbix-агент.
  8. Загрузіць шаблон у zabbix, ужыць яго да хаста, на якім ажыццяўляецца маніторынг і ўсталяваны zabbix-agent, паціснуць каля 5 хвілін, паглядзець што з'явіліся новыя элементы дадзеных, графікі і трыгеры.

Вынік

Выкарыстанне TSDuck для маніторынгу IP(TS)-струменяў

Для задачы выяўлення страты пакетаў амаль дастаткова, па меншай гэта лепш чым адсутнасць маніторынгу.

На самай справе, CC-«страты» могуць узнікаць пры склейцы відэафрагментаў (наколькі мне вядома, так робяцца ўстаўкі на мясцовых тэлецэнтрах у РФ, г.зн. без пераліку CC-лічыльніка), гэта трэба памятаць. У прапрыетарных рашэннях гэтая праблема часткова абыходзіцца дэтэктаваннем пазнак SCTE-35 пазнак (калі яны дадаюцца генератарам патоку).

З пункту гледжання маніторынгу якасці транспарту, не хапае маніторынгу jitter (IAT), т.я. ТБ-абсталяванне (няхай гэта будзе мадулятары або канчатковыя прылады) мае патрабаванні да гэтага параметру і не заўсёды можна раздзімаць jitbuffer да бясконцасці. А jitter можа паплысці калі на транзіце выкарыстоўваецца абсталяванне з вялікімі буферамі і не наладжаны ці недастаткова добра наладжаны QoS для перадачы падобнага realtime-трафіку.

Крыніца: habr.com

Дадаць каментар