Долго уже мучаюсь с гарантированной доставкой UDP пакетов через openvpn туннели, проходящие через двух разных операторов сотовой связи. Пока всё же остановился на TCP OpenVPN, как крайне рекомендовали. Но когда у одного из провайдеров начинается проблема доставки пакетов, разность во времени доставки пакетов по этим двум туннелям начинает порой доходить до 30 и более секунд. И принимающий VLC упорно не хочет работать по принципу "схватил №12345, далее ждёт именно №12346 и так далее", вместо этого он хватает то пакеты с одного интерфейса, то пакеты со второго интерфейса (для VLC приходит 2 копии трафика, iptables TEE, эти копии то и дело расходятся во времени, а так же некоторые пакеты одной из копий могут вообще не дойти, значительно реже недоходят некоторые пакеты с обоих копий)
Вопрос. Можно ли сделать какое-нибудь приспособление, которое на лету смогло бы упорядочивать пакеты по RTP sequence id, отбрасывая повторяющиеся пакеты? То есть это приспособление должно запоминать пришедшие данные и выпускать их, когда удастся установить порядок следования или станет ясно, что утерян недостающий пакет в обоих копиях трафика? Пока дошёл только до переключения между копиями через iptables DROP:
Код: Выделить всё
Date() { date +%Y-%m-%d_%H-%M-%S; }
tcpdump --immediate-mode -l -tt -T rtp -i any -n -v \
"udp and host 192.168.1.2 and host 192.168.1.20 and port 1234" | \
sed -u ':a; /)$/N; s/\n//; ta; s/,//g' | \
awk -v 'waittime=0.5' -v 'obzor=100' -v 'seqMax=65535' -v "seqMaxPolov=$((65535/2))" \
-v "conti=0" -v "timeGranica=-0.5" \
'{
print >> "/var/log/radio_tcpdump_source.log"
time = $1
eth = $6
pkgId = $8
inp = $18
out = $20
seqId = $24
if (ethStat == "") ethStat = eth
# Вычисляем, сколько пакетов потеряно с предыдущего принятого от этого интерфейса
inPackE[eth][inPackN[eth]] = seqId - inPack[eth][inPackN[eth]] - 1
if (inPackE[eth][inPackN[eth]] < 0)
inPackE[eth][inPackN[eth]] = inPackE[eth][inPackN[eth]] + seqMax + 1
if (inPackE[eth][inPackN[eth]] > 0)
print "log " time "\tКоличество пропусков:" inPackE[eth][inPackN[eth]] "\tSeqID:" seqId "\tУ интерфейса:" eth
inPackN[eth] ++
if (inPackN[eth] == obzor+1) inPackN[eth] = 1
inPack[eth][inPackN[eth]] = seqId
inPackAct[eth] = seqId
inPackActTime[eth] = time
# Перебор известных интерфейсов для подсчёта количества пропущенных пакетов
for (key1 in inPack) {
seqNOld = -1
inPackESumm[key1] = 0
# Перебор обозримых принятых пакетов на выбранном выше интерфейсе
for (key2 in inPack[key1]) {
key3 = obzor - inPackN[key1] + key2
if (key3 > obzor) key3 = key3 - obzor
# Суммируем ошибки для интерфейса
inPackESumm[key1] = inPackESumm[key1] + inPackE[key1][key3]
seqN = inPack[key1][key2]
seqNOld = seqN
}
}
# Находим альтернативный актуальному интерфейс
if (ethStatAlt == "") {
for (key1 in inPack) {
if (key1 != ethStat) {
ethStatAlt = key1
print ethStat " " inp " " out " " ethStatAlt
}
}
}
# Корректируем разницу seq numnber принятых пакетов по сбросу нумерации после 65535
inPackActRaznica = inPackAct[ethStat] - inPackAct[ethStatAlt]
inPackActRaznicaTime = inPackActTime[ethStat] - inPackActTime[ethStatAlt]
if (inPackActRaznica > seqMaxPolov) inPackActRaznica = seqMax - inPackActRaznica
if (inPackActRaznica < -seqMaxPolov) inPackActRaznica = -seqMax - inPackActRaznica
# Если inPackActRaznica имеет положительное значение,
# значит выбранный интерфейс опережает алтернативный на столько то пакетов, всё хорошо
if (inPackActRaznicaTime > timeGranica * -0.5 || inPackActRaznicaTime < -timeGranica * -0.5 || inPackESumm[ethStat] != 0)
print "log " time "\tethStat:" ethStat "\t" \
inPackAct[ethStat] "(" inPackActTime[ethStat] ")\t" \
inPackAct[ethStatAlt] "(" inPackActTime[ethStatAlt] \
")\tErr:" inPackESumm[ethStat] ":" inPackESumm[ethStatAlt] "\t" \
inPackActRaznica "\t:\t" inPackActRaznicaTime
if (inPackActRaznica < 10 && inPackESumm[ethStatAlt] <= inPackESumm[ethStat])
if ( (inPackActRaznicaTime < timeGranica) ||
(inPackESumm[ethStat] != 0 && inPackActRaznicaTime > timeGranica*4) )
{
tmp = ethStat
ethStat = ethStatAlt
ethStatAlt = tmp
print ethStat " " inp " " out " " ethStatAlt " " inPackActRaznicaTime ":" inPackESumm[ethStatAlt] ":" inPackESumm[ethStat]
}
fflush()
}' | while read eth in out downEthArr raznica
do
switch=0
numRM=""
in=${in%.*}
out=${out%.*}
if [[ $eth == "log" ]]; then
echo "$(Date) $eth $in $out $downEthArr Razn: $raznica" >> "$log"
continue
fi
echo >> "$log"
echo $(Date) $eth ${in} ${out} $downEthArr Raznica $raznica >> "$log"
# Разблокируем валидный поток, если заблокирован
numRM=( $(iptables -t raw -L PREROUTING -v -n --line | \
grep "^[ ]*[0-9][0-9]* .* tun_stream$eth .* $in .* $out .* dpt:1234") )
if [[ -n ${numRM[0]} ]]; then
iptables -t raw -D PREROUTING ${numRM[0]}
echo "$(Date) iptables -t raw -D PREROUTING ${numRM[0]}...${numRM[@]}"
switch=1
fi
# Блокируем невалидный поток
for downEth in $downEthArr; do
if ! iptables -t raw -L PREROUTING -v -n --line | \
grep -q "^[ ]*[0-9][0-9]* .* tun_stream$downEth .* $in .* $out .* dpt:1234"
then
iptables -t raw -L PREROUTING -v -n --line | \
grep -q "^[ ]*[0-9][0-9]* .* tun_stream$downEth .* $in .* $out .* dpt:1234" || \
iptables -t raw -A PREROUTING -p udp -s $in -d $out --dport 1234 -i tun_stream$downEth -j DROP
echo "$(Date) iptables -t raw -A PREROUTING -p udp -s $in -d $out --dport 1234 -i tun_stream$downEth -j DROP"
switch=1
fi
done
# Отправка сообщения, если произошло переключение
[[ $switch == 1 ]] && \
send "Звуковой поток переключил на tun_stream$eth" \
"denel@..." \
"Успешно переключил звуковой поток на tun_stream$eth
$(Date)"
iptables -t raw -L PREROUTING --line -n -v >> "$log"
done
Начинал же это "путишествие" здесь