====== Измерение пропускной способности канала: флудим по полной ======
**Дано:** есть арендованный оптический канал со скоростью 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
===== Эксперимент №1 - пользуемся средствами bash =====
#!/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 пакетов в секунду.
Ну даже в мегабиты переводить не имеет смысла - такой загрузки недостаточно для тестирования.
===== Эксперимент №2 - пользуемся программой netcat =====
В скрипте меняем только последнюю строку с циклом
#!/bin/sh
while true; do echo "$pkt"; done |nc -4u 192.168.10.25 5555
Результаты ну совсем мало отличаются от предыдущих: 29.93MiB 22.41K...
Тоже слабовато для тестирования.
===== Эксперимент №3 - пользуемся программой udpblast =====
Сначала укажем программе самой генерировать передаваемый трафик (ключ -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 гигабита), но настоящие джедаи никогда не остановятся на достигнутом, если можно улучшить результат.
===== Эксперимент №4 - делаем собственную программу генерации UDP-трафика =====
Настоящий джедай сам изготавливает себе световой меч. Ну и мы не будем отступать от светлого пути. Наскоро накидаем программку, отправляющие UDP-пакеты.
#include
#include
#include
#include
#include
#include
#include
char* server="192.168.10.25";
unsigned port=8888;
size_t buflen=1400;
void die(char *s) { perror(s); exit(1); }
char* MakeBuf(size_t s)
{
int i;
char* buf = (char*) malloc(s);
if (!buf) die("Can't allocate memory");
for (i = 0; i < s - 1; i++)
buf[i] = 'a' + rand()%26;
buf[i] = '\0';
return buf;
}
void Usage()
{
printf("Usage: udpsender [OPTIONS]\n"
"OPTIONS:\n"
"-d host - Destination host\n"
"-p port - Destination port\n"
"-s size - Packet size\n"
);
}
int ParceArgs(int argc, char *argv[])
{
int opt;
while((opt = getopt(argc, argv, "hp:d:s:")) != -1)
{
switch(opt)
{
case 'p':
port = atoi(optarg);
break;
case 's':
buflen = atoi(optarg);
break;
case 'd':
server = strdup(optarg);
break;
default:
Usage();
exit(1);
}
}
return 1;
}
int main(int argc, char *argv[])
{
struct sockaddr_in si;
int s, i;
ParceArgs(argc,argv);
char* testmsg = MakeBuf(buflen);
if ( (s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
{ die("Error: socket()"); }
memset((char *) &si, 0, sizeof(si));
si.sin_family = AF_INET;
si.sin_port = htons(port);
if (inet_aton(server , &si.sin_addr) == 0).
{ fprintf(stderr, "inet_aton() failed\n"); exit(1); }
while(1)
{
if ( sendto(s, testmsg, strlen(testmsg) , 0 , (struct sockaddr *) &si, sizeof(si)) == -1 )
{ die("Error: sendto()"); }
}
close(s);
return 0;
}
Компилируем исходный код
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.
===== Эксперимент №5 - делаем программу отправки трафика напрямую в сетевой интерфейс =====
Будем делать отправку данных не по ip, а генерировать ethernet-кадры и слать напрямую в сетевой интерфейс минуя обработку ip-пакетов системой.
В качестве документации берем статью [[https://austinmarton.wordpress.com/2011/09/14/sending-raw-ethernet-packets-from-a-specific-interface-in-c-on-linux/|Sending raw Ethernet packets from a specific interface in C on Linux]] и создаем такую программу
Поскольку программа пихает пакеты в интерфейс не определяя MAC-адреса получателя, то для данной программы **не требуется "отравлять" arp-кэш**.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_DEST_MAC0 0x00
#define MY_DEST_MAC1 0x00
#define MY_DEST_MAC2 0x00
#define MY_DEST_MAC3 0x00
#define MY_DEST_MAC4 0x00
#define MY_DEST_MAC5 0x00
char* server=(char*)"192.168.10.25";
unsigned port=8888;
size_t buflen=1500;
char ifName[IFNAMSIZ]="eth1";
void die(const char *s) { perror(s); exit(1); }
char* FillBuf(char* buf, size_t s)
{
int i;
for (i = 0; i < s - 1; i++)
buf[i] = 'a' + rand()%26;
buf[i] = '\0';
return buf;
}
void Usage()
{
printf("Usage: udprawsender [OPTIONS]\n"
"OPTIONS:\n"
"-i int - Network interface name\n"
"-d host - Destination host\n"
"-p port - Destination port\n"
"-s size - Packet size\n"
);
}
int ParceArgs(int argc, char *argv[])
{
int opt;
while((opt = getopt(argc, argv, "hp:d:s:i:")) != -1)
{
switch(opt)
{
case 'i':
strcpy(ifName, optarg);
break;
case 'p':
port = atoi(optarg);
break;
case 's':
buflen = atoi(optarg);
break;
case 'd':
server = strdup(optarg);
break;
default:
Usage();
exit(1);
}
}
return 1;
}
int main(int argc, char *argv[])
{
ParceArgs(argc,argv);
char* sendbuf = (char*) malloc(buflen);
if (!sendbuf) die("Can't allocate memory");
memset(sendbuf,0,buflen);
int sockfd;
struct ifreq if_idx;
struct ifreq if_mac;
struct ether_header *eh = (struct ether_header *) sendbuf;
struct iphdr *iph = (struct iphdr *) (sendbuf + sizeof(struct ether_header));
struct udphdr *udph = (struct udphdr *) (sendbuf + sizeof(struct ether_header) + sizeof(struct iphdr));
char* data = (char *) (sendbuf + sizeof(struct ether_header) + sizeof(struct iphdr) + sizeof(struct udphdr));
struct ifreq if_ip;
struct sockaddr_ll socket_address;
int ttl=250;
/* Open RAW socket to send on */
if ((sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) == -1) { perror("socket"); }
memset(&if_ip, 0, sizeof(struct ifreq));
strncpy(if_ip.ifr_name, ifName, IFNAMSIZ-1);
if (ioctl(sockfd, SIOCGIFADDR, &if_ip) < 0)
perror("SIOCGIFADDR");
/* Get the index of the interface to send on */
memset(&if_idx, 0, sizeof(struct ifreq));
strncpy(if_idx.ifr_name, ifName, IFNAMSIZ-1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) perror("SIOCGIFINDEX");
/* Get the MAC address of the interface to send on */
memset(&if_mac, 0, sizeof(struct ifreq));
strncpy(if_mac.ifr_name, ifName, IFNAMSIZ-1);
if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0) perror("SIOCGIFHWADDR");
/* Construct the Ethernet header */
/* Ethernet header */
eh->ether_shost[0] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[0];
eh->ether_shost[1] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[1];
eh->ether_shost[2] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[2];
eh->ether_shost[3] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[3];
eh->ether_shost[4] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[4];
eh->ether_shost[5] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[5];
eh->ether_dhost[0] = MY_DEST_MAC0;
eh->ether_dhost[1] = MY_DEST_MAC1;
eh->ether_dhost[2] = MY_DEST_MAC2;
eh->ether_dhost[3] = MY_DEST_MAC3;
eh->ether_dhost[4] = MY_DEST_MAC4;
eh->ether_dhost[5] = MY_DEST_MAC5;
/* Ethertype field */
eh->ether_type = htons(ETH_P_IP);
/* IP Header */
iph->ihl = 5; /* header length */
iph->version = 4;
iph->tos = 16; // Low delay
iph->tot_len = buflen;
iph->id = htons(54321);
iph->frag_off = 0x00; //16 bit field = [0:2] flags + [3:15] offset = 0x0
iph->ttl = ttl; //16 bit time to live (or maximal number of hops)
iph->protocol = 17; // UDP
iph->check = 0; //16 bit checksum of IP header. Can't calculate at this point
/* Source IP address, can be spoofed */
iph->saddr = inet_addr(inet_ntoa(((struct sockaddr_in *)&if_ip.ifr_addr)->sin_addr));
/* Destination IP address */
iph->daddr = inet_addr(server);
udph->source=htons(55555);
udph->dest=htons(port);
// udph->len
// udph->check
/* Packet data */
FillBuf(data, buflen - sizeof(struct ether_header) - sizeof(struct iphdr) - sizeof(struct udphdr));
/* Index of the network device */
socket_address.sll_ifindex = if_idx.ifr_ifindex;
/* Address length*/
socket_address.sll_halen = ETH_ALEN;
/* Destination MAC */
socket_address.sll_addr[0] = MY_DEST_MAC0;
socket_address.sll_addr[1] = MY_DEST_MAC1;
socket_address.sll_addr[2] = MY_DEST_MAC2;
socket_address.sll_addr[3] = MY_DEST_MAC3;
socket_address.sll_addr[4] = MY_DEST_MAC4;
socket_address.sll_addr[5] = MY_DEST_MAC5;
while (1)
{
/* Send packet */
if (sendto(sockfd, sendbuf, buflen, 0, (struct sockaddr*)&socket_address, sizeof(struct sockaddr_ll)) < 0)
die("Send failed\n");
}
return 0;
}
Компилируем:
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
{{tag>linux shell Си network}}