Инструменты пользователя

Инструменты сайта


net:channel_performance_measuring

Это старая версия документа!


Измерение пропускной способности канала: флудим по полной

Дано: есть арендованный оптический канал со скоростью 2 гигабита по договору. С одной стороны этот канал можно воткнуть в сервер, другая сторона находится далеко и разместить там измерительное оборудование не представляется возможным. С удаленной стороны канал подключен к коммутатору с которого можно снять значения приходящего из канала трафика.

Требуется: проверить что обещанные 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

Кроме всего прочего надо иметь в виду, что 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

netflood1.sh
#!/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

В скрипте меняем только последнюю строку с циклом

netflood2.sh
#!/bin/sh
 
while true; do echo "$pkt"; done |nc -4u 192.168.10.25 5555

Результаты ну совсем мало отличаются от предыдущих: 29.93MiB 22.41K…

Тоже слабовато для тестирования.

Эксперимент №3 - пользуемся программой udpblast

Сначала укажем программе самой генерировать передаваемый трафик (ключ -a)

netflood3.sh
#!/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

А теперь будем отправлять заранее подготовленные пакеты

netflood3-1.sh
#!/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-пакеты.

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

udpsender.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <time.h>
#include <unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
 
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-пакетов системой.

В качестве документации берем статью Sending raw Ethernet packets from a specific interface in C on Linux и создаем такую программу

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

rawudpsender.c
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
 
#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;
 int tx_len = 0;
 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);
 tx_len += sizeof(struct ether_header);
 
 /* 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);
 tx_len += sizeof(struct iphdr);
 
 udph->source=htons(55555);
 udph->dest=htons(port);
// udph->len
// udph->check
 tx_len += sizeof(struct udphdr);
 
 /* 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.

Улучшим результат запустив флудилку в многопоточном режиме

netflood.sh
#!/bin/bash
 
threads=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, если так сделать, то запущенная в много потоков флудилка не будет завершена и останется флудить после прерывания скрипта.

net/channel_performance_measuring.1646116039.txt.gz · Последнее изменение: 2022/03/01 09:27 — san

Если не указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: Public Domain
Public Domain Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki