Автоматизация IP-сети. Часть3 – Мониторинг TCP аномалий

    image alt В предыдущей статье рассмотрен мониторинг скорости открытия Веб ресурсов. В качестве параллельного процесса при измерении скорости, для более глубокого понимания возможных причин низкой скорости открытия Веб страниц, было бы интересно провести измерение TCP аномалий. Эту задачу попробуем решить в этой статье.

    TCP аномалиями будем считать пакеты, которые свидетельствуют о потери информации в процессе передачи. Пожалуй, наиболее популярным инструментом глубокого анализа сетевого трафика, является утилита Wireshark и ее консольная версия tshark. Поэтому в качестве исходного анализатора будем рассматривать именно ее.
    К TCP аномалиям будем относить следующее:

    • tcp retransmission – происходит, когда отправитель повторно передает пакет по истечении срока подтверждения;
    • tcp duplicate_ack – происходит, когда отображается один и тот же номер ACK и он меньше последнего байта данных, отправленных отправителем. Если приемник обнаруживает пробел в порядковых номерах, он будет генерировать дубликат ACK для каждого последующего пакета, который он получает по этому соединению, до тех пор, пока недостающий пакет не будет успешно принят (повторно передан);
    • tcp lost_segment — происходит, когда есть разрыв в порядковых номерах пакетов. Потеря пакетов может привести к дублированию ACK, что приведет к повторным передачам;
    • tcp.analysis.fast_retransmission — возникает, когда отправители получают несколько пакетов, порядковый номер которых больше, чем подтвержденные пакеты, в этом случае отправитель повторно передает пакет до истечения таймера подтверждения;
    • — tcp ack_lost_segment — происходит, когда есть разрыв в порядковых номерах подтверждающих пакетов.

    Для анализа пакетов при помощи tshark будем использовать следующее выражение:

    tshark -i bce0 -t ad -qz io,stat,5,"(ip.addr==1.1.1.1) && tcp","COUNT(tcp.analysis.retransmission)(ip.addr==1.1.1.1) && tcp.analysis.retransmission","COUNT(tcp.analysis.duplicate_ack)(ip.addr==1.1.1.1) && tcp.analysis.duplicate_ack","COUNT(tcp.analysis.lost_segment)(ip.addr==1.1.1.1) && tcp.analysis.lost_segment","COUNT(tcp.analysis.fast_retransmission)(ip.addr==1.1.1.1) && tcp.analysis.fast_retransmission","COUNT(tcp.analysis.lost_segment)(ip.addr==1.1.1.1) && tcp.analysis.ack_lost_segment")

    , где
    bce0 – это название интерфейса на которому будет производится анализ пакетов. Это название в linux/Unix система можно увидеть командой ifconfig.
    1.1.1.1 – IP адрес исследуемого ресурса

    В результате вывода команды получаем таблицу, которую в последствии будем обрабатывать и подгружать в базу для построения графиков:

    Таблица
    ======================================================================================================
    | IO Statistics                                                                                      |
    |                                                                                                    |
    | Duration: 5. 40977 secs                                                                            |
    | Interval: 5 secs                                                                                   |
    |                                                                                                    |
    | Col 1: (ip.addr==1.1.1.1) && tcp                                                            |
    |     2: COUNT(tcp.analysis.retransmission)(ip.addr==1.1.1.1) && tcp.analysis.retransmission  |
    |     3: COUNT(tcp.analysis.duplicate_ack)(ip.addr==1.1.1.1) && tcp.analysis.duplicate_ack    |
    |     4: COUNT(tcp.analysis.lost_segment)(ip.addr==1.1.1.1) && tcp.analysis.lost_segment      |
    |     5: COUNT(tcp.analysis.fast_retransmission)(ip.addr==1.1.1.1) &&                         |
    |        tcp.analysis.fast_retransmission                                                            |
    |     6: COUNT(tcp.analysis.lost_segment)(ip.addr==1.1.1.1) && tcp.analysis.ack_lost_segment  |
    |----------------------------------------------------------------------------------------------------|
    |                     |1                |2      |3      |4      |5      |6      |                    |
    | Date and time       | Frames |  Bytes | COUNT | COUNT | COUNT | COUNT | COUNT |                    |
    |-------------------------------------------------------------------------------|                    |
    | 2017-07-10 15:00:45 |    507 | 481496 |     1 |     0 |     2 |     0 |     0 |                    |
    | 2017-07-10 15:00:50 |      0 |      0 |     0 |     0 |     0 |     0 |     0 |                    |
    ======================================================================================================

    В качесте инструментов как и в прошлой статье будем использовать Cacti и Python3. Модернизируем скрипт из предыдущей статьи, для измерения скорости и TCP аномалий:

    Код Python
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import datetime
    import re
    import os
    import subprocess
    import argparse
    import time
    import signal
    
    parser = argparse.ArgumentParser()
    parser.add_argument("-h_page", "--hostname_page", dest = "hostname_page")
    args = parser.parse_args()
    
    curent_time=str(datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S_"))
    pid=os.getpid()
    
    
    ##########start Table parser###############
    def parser_tshark_output(open_time, parser_file):
            table_data = {'date':[],'1 frame':[],'2 frame':[],'retr':[],'dup_ack':[],'lost_seg':[],'fast_retr':[],'ack_lost_seg':[]}
            lines = open(parser_file, 'r').readlines()
            lookup = 'tcp.analysis.ack_lost_segment '
            number = 0
            for num in lines:
                    if lookup in num:
                            number+=lines.index(num)
                            try:
                                    del (lines[-1])
                            except:
                                    pass
            L=open("/usr/TEST/TMP_FILES/test_tshark_temp_"+str(os.getpid())+".txt", 'w')
            L.writelines(lines)
            L.close()
            with open("/usr/TEST/TMP_FILES/test_tshark_temp_"+str(os.getpid())+".txt", 'r') as table:
                    if number != 0:
                            for _ in range(int(number+5)):
                                    next(table)  #skip header
                            for row in table:
                                    row=row.strip('\n').split('|')
                                    values = [r.strip() for r in row if r != '']
                                    table_data['date'].append(values[0])
                                    table_data['1 frame'].append(int(values[1]))
                                    table_data['2 frame'].append(int(values[2]))
                                    table_data['retr'].append(int(values[3]))
                                    table_data['dup_ack'].append(int(values[4]))
                                    table_data['lost_seg'].append(int(values[5]))
                                    table_data['fast_retr'].append(int(values[6]))
                                    table_data['ack_lost_seg'].append(int(values[7]))
                            else:
                                            pass
            if number !=0:
                    frames=sum(table_data['2 frame'])
                    print ('start-frames_' + str.format("{0:.2f}", frames)+'_end-frames')
                    tcp_errors=sum(table_data['retr'])+sum(table_data['dup_ack'])++sum(table_data['fast_retr'])+sum(table_data['ack_lost_seg'])
                    print ('start-tcp_errors_' + str(tcp_errors)+'_end-tcp_errors')
                    if open_time != 'open_error' and frames != 0:
                            k=tcp_errors
                    else:
                            k= 'no data'
            else:
                    k= 'no data'
            os.remove("/usr/TEST/TMP_FILES/test_tshark_temp_"+str(os.getpid())+".txt")
            return (k)
    
    ###########end table parser###############
    ###########start tshark part 1###########
    resault_temp=subprocess.Popen(['nslookup '+str(args.hostname_page)], bufsize=0, shell=True, stdout = subprocess.PIPE, stderr=subprocess.PIPE)
    data=resault_temp.communicate()
    ip_adr_temp=re.findall(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', str(re.findall(r'Address: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', str(data))))
    if ip_adr_temp != []:
            pass
    else:
            ip_adr_temp = ['1.1.1.1']
    ip_adr=''
    for z in ip_adr_temp:
            if len(ip_adr_temp)-ip_adr_temp.index(z)==1:
                    ip_adr+='ip.addr=='+str(z)+')'
            else:
                    ip_adr+='ip.addr=='+str(z)+' or '
    
    ftcp=open('/usr/TEST/TMP_FILES/1tshark_temp'+curent_time+str(pid)+'.txt', 'w')
    tcp=subprocess.Popen(['timeout 180 tshark -i bce0 -t ad -qz io,stat,5,"('+str(ip_adr)+' && tcp","COUNT(tcp.analysis.retransmission)('+str(ip_adr)+' && tcp.analysis.retransmission","COUNT(tcp.analysis.duplicate_ack)('\
    +str(ip_adr)+' && tcp.analysis.duplicate_ack","COUNT(tcp.analysis.lost_segment)('+str(ip_adr)+' && tcp.analysis.lost_segment","COUNT(tcp.analysis.fast_retransmission)('+str(ip_adr)+\
    ' && tcp.analysis.fast_retransmission","COUNT(tcp.analysis.lost_segment)('+str(ip_adr)+' && tcp.analysis.ack_lost_segment"']\
    , bufsize=0, shell=True, stdout=(ftcp))#stdout = subprocess.PIPE, stderr=subprocess.PIPE)
    time.sleep(2)
    
    ############start wget ################
    
    
    fweb=open('/usr/TEST/TMP_FILES/web_temp'+curent_time+str(pid)+'.txt', 'w')
    web=subprocess.call(["timeout 120 wget -E -H -p -Q300K --user-agent=Mozilla --no-cache --no-cookies --delete-after --timeout=15 --tries=2 "+args.hostname_page+" 2>&1 | grep '\([0-9.]\+ [KM]B/s\)'"], bufsize=0, shell=True, stdout=(fweb))
    fweb.close()
    fweb=open('/usr/TEST/TMP_FILES/web_temp'+curent_time+str(pid)+'.txt', 'r')
    data=fweb.read()
    os.remove('/usr/TEST/TMP_FILES/web_temp'+curent_time+str(pid)+'.txt')
    speed_temp=re.findall(r's \((.*?)B/s', str(data))#[KM]B/s', str(data)))
    speed_temp_si=re.findall(r's \((.*?) [KM]B/s', str(data))
    try:
            if re.findall(r'M', str(speed_temp))==[] and re.findall(r'K', str(speed_temp))==[]:
                    speed_="{0:.3f}".format(float(speed_temp_si[0])*0.001*8)
            elif re.findall(r'M', str(speed_temp))!=[]:
                    speed_="{0:.3f}".format(float(speed_temp_si[0])*1000*8)
            elif re.findall(r'K', str(speed_temp))!=[]:
                    speed_="{0:.3f}".format(float(speed_temp_si[0])*1*8)
    except:
            speed_='no_data'
    ##############stop wget##############
    ##############start tshark part2#######
    
    os.kill(tcp.pid, signal.SIGINT)
    ftcp.close()
    time.sleep(0.3)
    tcp_error=parser_tshark_output('1', '/usr/TEST/TMP_FILES/1tshark_temp'+curent_time+str(pid)+'.txt')
    os.remove('/usr/TEST/TMP_FILES/1tshark_temp'+curent_time+str(pid)+'.txt')
    #########resault to DB###########
    print ('web_speed_test:'+str(speed_)+' tcp_error:'+str(tcp_error))
    


    Запуск скрипта должен показать следующее (в операционной системе должны быть установлены утилиты tshark, nslookup, wget):

    $python3.3 web_open.py -h_page habrahabr.ru
    web_speed_test:10960.000 tcp_error:2.0

    Далее краткая инструкция как модернизировать данные в Cacti для получения двух графиков с одного RRA:

    Cacti
    1. Добавляем дополнительную output Fields в Data Input Methods:



    2. Добавляем в Data Temlate дополнительную область tcp_error



    3. Копируем Graph template и добавляем в него дополнительную информацию, при заведении новых графиков изначально нужно использовать этот Template:



    4. Заводим график как показано в предыдущей статье с использование Template выше, график дублируем:



    5. в итоге должно получится следующее:



    6. Меняем данные в первом графике на правельные. Сначала меняем Template жмем save, затем проверяем что Data Source верный, при необходимости выбираем правильный:



    7. Меняем данные для второго графика:




    Если все сделано верно, должны получится следующие графики





    На этом все, далее по плану при положительных отзывах, в следующих статьях рассмотрим модернизацию данной статистики.

    • Добавление TCP и ICMP RTD (round trip delay).
    • Вынос измерений на отедельные пробники под управлением головного сервера с системой визуализации и базой даных.
    • Возможность тестирования ICMP RTD с любого маршрутизатора сети (Cisco, Juniper, Huawei).

    Спасибо за Ваше время.
    • +5
    • 7,9k
    • 4
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 4
    • +1
      Несколько минут тупил, пытаясь понять логику работы «всего в целом». Понял следующее:
      1. Периодически запускаем скрипт, который скачивает фрагмент данных из Интернета.
      2. Параллельно с п. 1 на этом же хосте запускаем tshark для определения количества ошибок/аномалий tcp.
      3. Выводим эти данные на графики посредством Cacti.
      Вижу один минус — сайт в п. 1 всякий раз один и тот же. Можно сделать несколько разных, но сути не меняет — нерепрезентативно.
      Я бы, наверное, вместо скрипта из п. 1 оборудовал зеркальный порт для всего трафика нашей сети и периодически снимал бы с него данные тем же tshark-ом. Дальше — как в статье.
      И вопросы: на картинках реальные графики? Какие выводы можно из них делать (типа, какие значения должны быть в нормальной ситуации, а когда уже пора чинить сеть)?
      • 0
        — Максим почему всякий раз один и тот же сайт? В данном случае с GUI Cacti при добавлении нового графика указывается адрес страницы вот скрин — https://hsto.org/web/654/316/0e5/6543160e5282454b95b26133d4cfe004.jpg. Каждый график — свой сайт.
        — Графики реальные, по TCP ошибкам пока в стадии наблюдения, не было возможности проверить при каких то негативных событиях. По web speed, как раз таки на графике видно снижение скорости, админ сайта подтвердил снижение производительности ввиду проводимых работ.
        — по зеркалированию всего трафика, идея интересная, если трафика не много и коррелировать данные по скорости и ошибкам не нужно.
        • 0
          Вот этот момент я и хотел уточнить — на графике мы видим не число TCP-ошибок в сети вообще, а число ошибок при доступе к данному конкретному сайту.
          Если мы хотим получить картину «по Интернету в целом» — нужно волевым решением определить несколько сайтов в разных локациях, и отрисовать по ним графики. В нашей компании мы мониторим работу операторов приблизительно так же.
      • +2

        Открывая статью, ожидал снятие счётчиков из sysfs/procfs, трассировку perf/ftrace, на крайняк парсинг выхлопа утилит показа статистики. Но к такому меня жизнь не готовила :-)

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.