====== Измерение пропускной способности канала: флудим по полной ====== **Дано:** есть арендованный оптический канал со скоростью 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}}