Дано: есть арендованный оптический канал со скоростью 2 гигабита по договору. С одной стороны этот канал можно воткнуть в сервер в котором есть 10G оптическая сетевуха, другая сторона находится далеко и разместить там измерительное оборудование не представляется возможным. С удаленной стороны канал подключен к коммутатору с которого можно снять значения приходящего из канала трафика.
Требуется: проверить что обещанные 2 гигабита предоставляются арендованным каналом.
Решение:
Если бы можно было разместить с удаленной стороны второй сервер, то можно было бы воспользоваться программами iperf3, netperf и другими подобными. И задача была бы решена. Поскольку такое невозможно по условию задачи (указанные программы требуют обязательной ответной части, которую разместить негде), то будем как-то изголяться.
Можно загрузить канал по максимуму с ближней стороны исходящим трафиком с сервера и замерять приходящий трафик на коммутаторе.
Загрузить канал можно например UDP-трафиком. Тоесть нужно найти решение предоставляющее возможность нафлудить в канал по максимуму. Данная методика не является идеальной, поскольку мы сможем загрузить канал только в одном направлении. Однако за неимением лучшего и принимая во внимание, что ассиметричный шейпинг тестируемого канала маловероятен будем довольствоваться тем что можем сделать.
Итак, начинаем изыскания:
Настроем тестируемую сеть. На интерфейсе eth1, который подключен к тестируемому каналу выставим какой-нибудь ip-адрес. Будем использовать 192.168.10.1/24. Отправлять пакеты будем на адрес 192.168.10.25
С противоположной стороны будут сниматься показания со счетчиков порта на коммутаторе и никакой настройки не требуется.
Для того, чтоб удостовериться в достаточной загрузке канала со стороны сервера, будем смотреть загрузку интерфейса eth1. Для этого вполне подойдет прогамма bmon, которую будем запускать следующим образом
bmon -p eth1 -o curses:ngraph=2,curses:details,curses:info
eth1 bmon 4.0 Interfaces │ RX bps pps %│ TX bps pps % >eth1 │ 0 0 │ 1.03GiB 733.75K ───────────────────────────────┴───────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── B (RX Bytes/second) MiB (TX Bytes/second) 133.00 ..................|......................................... 1090.03 |||||||||||||||||||||....................................... 110.83 ..................|......................................... 908.36 |||||||||||||||||||||....................................... 88.67 ..................|......................................... 726.68 |||||||||||||||||||||....................................... 66.50 ..................|......................................... 545.01 ||||||||||||||||||||||...................................... 44.33 ..................|......................................... 363.34 ||||||||||||||||||||||...................................... 22.17 ..................|......................................... 181.67 ||||||||||||||||||||||...................................... 1 5 10 15 20 25 30 35 40 45 50 55 60 1 5 10 15 20 25 30 35 40 45 50 55 60 (RX Packets/second) K (TX Packets/second) 1.00 ..................|......................................... 761.92 |||||||||||||||||||||....................................... 0.83 ..................|......................................... 634.93 |||||||||||||||||||||....................................... 0.67 ..................|......................................... 507.94 |||||||||||||||||||||....................................... 0.50 ..................|......................................... 380.96 ||||||||||||||||||||||...................................... 0.33 ..................|......................................... 253.97 ||||||||||||||||||||||...................................... 0.17 ..................|......................................... 126.99 ||||||||||||||||||||||...................................... 1 5 10 15 20 25 30 35 40 45 50 55 60 1 5 10 15 20 25 30 35 40 45 50 55 60
Кроме всего прочего надо иметь в виду, что ip-трафик не пойдет в интерфейс, если операционная система не будет знать, что с другой стороны канала есть устройство, способное принимать данные. Будут отправляться arp-запросы, на которые некому отвечать и больше никакого трафика. Чтобы эту проблему решить "отравим" arp-кэш - подставим в него липовые данные (любой мак-адрес для ip-адреса 192.168.10.25)
arp -s 192.168.10.25 78:e3:b5:f5:7a:DD
После окончания экспериментов следует удалить из арп-кэша фиктивное значение.
arp -d 192.168.10.25
#!/bin/bash # Будем делать отправку большими блоками данных по 64КБ buflen=65535 # Формируем блок данных, заполняя его случайными цифробуквенными данными pkt="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $buflen| head -n 1)" # отправляем в бесконечном цикле UDP-трафик на ip 192.168.10.25 и # порт 5555 (можно выбрать любой - не принципиально) while true; do echo "$pkt"; done >/dev/udp/192.168.10.25/5555
Смотрим в bmon и видим такие результаты 29.41MiB/сек 20.69K пакетов в секунду.
Ну даже в мегабиты переводить не имеет смысла - такой загрузки недостаточно для тестирования.
В скрипте меняем только последнюю строку с циклом
#!/bin/sh while true; do echo "$pkt"; done |nc -4u 192.168.10.25 5555
Результаты ну совсем мало отличаются от предыдущих: 29.93MiB 22.41K…
Тоже слабовато для тестирования.
Сначала укажем программе самой генерировать передаваемый трафик (ключ -a)
#!/bin/sh buflen=65535 pkt="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $buflen| head -n 1)" udpblast -4qa -s 32kb -c1m 10.48.1.25:5555
Результаты: 61.66MiB 44.31K
А теперь будем отправлять заранее подготовленные пакеты
#!/bin/sh buflen=65535 pkt="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $buflen| head -n 1)" echo "$pkt" |udpblast -4q -s 32kb -c1m 10.48.1.25:5555
Результаты: 550.48MiB 395.61K … хм-м-м-м… а вот это уже что-то. 550*8/1024=4,296875 гигабита/сек.
Можно запустить команду генерирующую трафик в несколько потоков и еще улучшить результат.
В принципе поставленная задача решена (нам надо проверить полосу в 2 гигабита), но настоящие джедаи никогда не остановятся на достигнутом, если можно улучшить результат.
Настоящий джедай сам изготавливает себе световой меч. Ну и мы не будем отступать от светлого пути. Наскоро накидаем программку, отправляющие UDP-пакеты.
Компилируем исходный код
gcc -O3 -static udpsender.c -o udpsender
Запускаем тестирование (шлем пакеты на 192.168.10.25 на порт 5555 размер пакетов максимальный - 65508):
./udpsender -d 192.168.10.25 -p 5555 -s 65508
Результаты: 485.53MiB 341.71K - немного ниже чем у udpblast.
Будем делать отправку данных не по ip, а генерировать ethernet-кадры и слать напрямую в сетевой интерфейс минуя обработку ip-пакетов системой.
В качестве документации берем статью Sending raw Ethernet packets from a specific interface in C on Linux и создаем такую программу Поскольку программа пихает пакеты в интерфейс не определяя MAC-адреса получателя, то для данной программы не требуется "отравлять" arp-кэш.
Компилируем:
gcc -O3 -static rawudpsender.c -o rawudpsender
Запускаем:
./udprawsender -d 192.168.10.25 -p 5555 -s 1500 -i eth1
В качестве параметров указываем ip-адрес и порт (это все как раньше). Указываем имя интерфейса в который будем отправлять пакеты. И что важно - размер пакета следует задать равным MTU для данного сетевого интерфейса (меньше будет непродуктивно, больше не войдет в интерфейс).
Результаты: 551.42MiB 385.45K - максимально приближено к результатам udpblast.
Улучшим результат запустив флудилку в многопоточном режиме
#!/bin/bash threads=32 # будем запускать в 32 потока cmd="udprawsender" param="-d 192.168.10.25 -p 5555 -s 1500 -i eth1" for i in $(seq 1 $threads) do "./$cmd" $param & pid[$i]=$! echo "Run thread $i - pid: ${pid[$i]}" sleep 0.1 done read -p "Flood Started! Press enter to terminate flood" for i in $(seq 1 $threads); do kill -9 ${pid[$i]}; done
После запуска всех потоков флудилки скрипт ожидает нажатия Enter, после которого завершает запущенные потоки. Поэтому скрипт не рекомендуется прерывать по Ctrl+C, если так сделать, то запущенная в много потоков флудилка не будет завершена и останется флудить после прерывания скрипта. Если скрипт был неправильно завершен, то придется прибивать запущенные флудилки с помощью "killall -9".
Число потоков флудилки 32 было подобрано опытным путем. Начиная с 8 увеличивал, пока результаты не перестали заметно увеличиваться.
Результаты: 1.07GiB 766.91K О! 1,07×8=8,56 гигабит в секунду, 766К пакетов в секунду. Пока что это максимум чего удалось достигнуть.
Я пробовал запускать udpsender в много потоков. Результаты получались почти такие же как у отправлялки сырых пакетов, однако udpsender при этом заметно сильнее нагружает систему. Load Average подскакивает очень заметно пропорционально числу потоков флудилки.
При выполнении задачи описанной в статье важно озаботиться тюнингом сетевой карты. Как минимум увеличить размер очереди отправки:
ip link set dev eth1 txqueuelen 10000
Если на сетевой карте выставить MTU в 9000, то можно увеличить загрузку однопоточным зафлуживанием канала. В многопоточном варианте все-равно все упирается в потолок 1.08GiB/s, но зато можно снизить число запускаемых потоков для достижения этого "потолка".
Результатов превышающих абсолютно все описанные удалось добиться при использовании udpblast в многопоточном режиме 1.16GiB 150.92K 1,16*8=9,28 гигабит в секунду, что близко к максимальной полосе испытываемого 10гигабитного интерфейса, однако все остальные эксперименты были тоже интересны.
Вот скрипт многопоточного флуда с помощью tcpblast
#!/bin/bash threads=32 buflen=65535 pkt="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $buflen| head -n 1)" for i in $(seq 1 $threads) do (echo "$pkt" |udpblast -4q -s 32kb -c1m 192.168.10.25:5555) & echo "Run thread $i" sleep 0.1 done read -p "Flood Started! Press enter to terminate flood" killall -9 udpblast